Muddy WebはMuddy = 泥臭いとして、Webフロントエンドの開発現場における話やケーススタディなど泥臭さのある話から、学びを得ることを目的として開催しています。
現場で遭遇した具体的な体験を元に、実際に明日から使えるかもしれないWebフロントエンドの技術や知識を、参加者の皆さまと共有し合うことを通して、フロントエンド開発の糧になれればと思います。
第11回はゲストに株式会社Cybozu様をお招きし、Webフロントエンドの現場から明日使えそうな技術や事例をトークしました。
本記事は、2025年03月21(金)に開催した「Muddy Web #11 ~Special Edition~ 【ゲスト: Cybozu】」において発表された「ReactFlow への移行で実現するユーザー体験と開発体験の向上」に対して、社内の生成AI議事録ツール「コエログ」を活用して書き起こし、登壇者本人が監修役として加筆修正しました。
吉田純基
2024 年 2 月に株式会社サイバーエージェントに入社。株式会社 AI Shift に所属し、ソフトウェアエンジニアとして開発に従事。
X: https://x.com/__yoshi_dev
今回のタイトルは「React Flowへの移行で実現するユーザー体験と開発体験の向上」という内容でお話させていただきます。よろしくお願いいたします。
まずは自己紹介をさせてください。私は吉田と申します。2024年に株式会社サイバーエージェントに中途入社し、現在はWebフロントエンドエンジニアとして株式会社AI SHIFTに所属しています。現在は、AIエージェント構築プラットフォームであるAI Workerの開発に携わっています。
本日のアジェンダになります。今回は、Rete.jsやReact Flowの詳細な機能やAPI仕様については触れません。また、生成AIやAIエージェントの詳細な技術的背景についても、今回は割愛させていただきます。
まず、AI Workerについて少し説明させてください。AI Workerとは、企業専用のAIエージェント構築プラットフォームです。企業ごとの業務ニーズや業務プロセスに合わせて、柔軟にAIエージェントの構築および運用を実現するためのプラットフォームとなっています。
このプロダクトは、サイバーエージェントが独自に培ってきた大規模言語モデルの開発知見と、これまでに蓄積してきた生成AIや業務自動化支援の経験を生かして開発されたものです。
今回お話しするのは、AI Workerというプロダクトの中に含まれる「ワークフロー」という機能についてです。このワークフロー機能では、LLMの組み合わせによってルールベースに処理の流れを構築していくことができます。ユーザーがある程度定義した内容に沿って、マニュアル的に実行されていく仕組みになっているのが特徴です。
AI Workerの技術スタックについても、簡単にご紹介させていただきます。
画面左側がフロントエンド、右側がバックエンドの構成になりますが、フロントエンド・バックエンドともにTypeScriptで開発を行っています。フロントエンドに関しては、ReactとRete.jsを採用しており、クライアントサイドのみでレンダリングを行う構成となっています。
まずは、移行前に使用していたライブラリの概要と、当時それを採用していた理由についてお話しします。Rete.jsは、視覚的なノードベースエディターを構築するためのTypeScript-Firstなフレームワークです。UIだけでなく、プリセットやプラグイン、ユーティリティモジュールといった拡張機能も用意されているのが特徴です。
このライブラリを採用した理由としては、開発当時のワークフロー機能の主な用途が、LLMの精度検証であり、社内利用に限定されていたことが背景にあります。そのため、まずはスピード感を持ってプロトタイプ的に開発を進める必要があり、比較的早く立ち上げられるRete.jsを選定しました。
そもそも、なぜReact Flowへの移行を決めたのかというと、主な理由は「利用者の変化」にあります。生成AIの業界は非常に変化のスピードが速く、事業方針の転換やピボットが頻繁に発生します。その中で、ワークフロー機能の利用者層にも変化があり、より幅広いお客様がこの機能を使うようになったというのが背景にあります。
もともとの想定では、前のページでも触れたように、LLMの精度検証を目的とした社内利用が中心でした。しかし事業方針の変更により、LLMの精度検証に加えて、お客様自身がワークフローを構築し、それをAPIとして外部に公開するという新しいユースケースが出てきました。つまり、AI Workerのプラットフォーム内だけでなく、APIとして他のシステムと連携して利用されるケースが生まれてきたということです。
これにより、利用シーンが社内に閉じたものから、導入先のお客様にも広がるようになり、ユーザー体験の質をこれまで以上に重視する必要が出てきました。その課題を解決する手段として、React Flowへの移行を決断しました。
まず、移行を検討するに至った課題についてお話しします。Rete.jsを使ってワークフロー自体を構築することは可能だったのですが、社内でドッグフーディングを行い、アンケートを取った結果、特にUI/UXに関する改善要望が多く寄せられました。
たとえば、ピンチズームやマウスホイールによるズーム操作の精度が低いといった点が挙げられ、ユーザー体験に課題があることが明らかになりました。もちろん、これはRete.jsというライブラリを自分自身が十分に使いこなせていなかったという側面もあるとは思いますが、それでもUI/UXに関する改善要望は全体で69件も集まり、課題の大きさを実感しました。
こちらの画像は、実際にRete.jsを使用していた際のサンプル画面をキャプチャしたものになります。見ていただくとわかる通り、ユーザーとしては比較的緩やかにピンチズームの操作をしているのですが、実際の画面上では意図以上にズーム速度が速くなってしまい、細かい調整がしづらいという問題がありました。
さらにもう一点、dependency周りにも課題がありました。Rete.jsには公式のReactプラグインとして「rete-react-plugin」が用意されているのですが、その中でstyled-componentsに依存している構成になっていました。AI WorkerではTailwind CSSを採用しており、styled-componentsは完全にRete.jsのためだけに追加していた依存関係になってしまっていました。
また、Rete.jsはコア機能以外の部分を多くのプラグインに依存して構成する仕組みになっており、プロダクションでしっかり使うには必要なプラグインを個別にインストールしていく必要がありました。これによって、最終的にはdependencyの数がかなり多くなり、Renovateなどのツールでのアップデート対応も煩雑になってしまい、メンテナンスコストが大きくなってしまっていたという課題がありました。
一旦、課題を整理すると、大きく2つの方向性が見えてきました。ひとつは、既存のライブラリが提供する機能を拡張しながら、必要な部分を独自実装で補っていく方法。もうひとつは、新たなライブラリを採用し、そこに標準で用意されている機能を活用して課題を解決していくという方法です。
その中で、React Flowが提供しているデフォルト機能を見たときに、今回直面していた課題に対して十分に対応できそうだと判断できたため、後者の「React Flowへの移行」を選択するという決断に至りました。これが、ライブラリ移行の背景になります。
React Flowの概要と機能について少し説明させてください。React FlowもRete.jsと同じくノードベースのエディターを提供しているライブラリになります。特徴としては、優れたUX、ドラッグ&ドロップだったりとか、ズームとかパンニングなどの直感的な操作感があったりとか、あとは高いカスタマイズ性だったりとか、豊富な組み込みコンポーネント、ミニマップとかノードリサイザーなどをビルトインとして提供している点があります。
React Flowの採用理由として挙げられるのは、すでに他のプロダクトで利用実績があったという点です。例えば、Stripeなどでも使用されており、またここには記載していませんが、Difyという似たようなアプリケーションにおいてもワークフロー機能が提供されており、その内部でReact Flowが使われているという事例がありました。そうした実績が、採用理由のひとつになっています。
また、React Flowはドキュメントサイトに豊富なexampleが用意されており、参考にしやすいという点も大きな魅力です。さらに、shadcn/uiを用いた構築例も存在しており、技術的にAI Workerとの相性が非常に良かったという点も、採用を後押しした理由のひとつです。
その他の理由としては、React Flowが豊富なユーティリティやカスタムフックを提供しており、Reactベースの開発において、React Flow内部の状態をカスタムフック経由で簡単に取得できるという点があります。また、弊社内においてもAI Worker以外のプロダクトですでに採用実績があり、社内で困ったときに相談しやすい環境が整っていたことも、採用を決める上での後押しとなりました。
では、React Flowへの移行を決定したということで、次は移行計画に着手しました。まずマスト要件としてあったのは、今回のReact Flowへの移行が非機能要件であったため、なるべく早くタスクとして完了させる必要がありました。具体的には、約1ヶ月以内に移行を完了させることが求められていました。
また、既存のUIやワークフロー構造を維持しながら移行を進めること、そしてReact Flow移行による恩恵を受けるために、UXが確実に改善されることが求められる要件として挙げられていました。
それらを踏まえたうえで、やるべきこと、つまりTODOとしては、まずUIの再実装、次に既存データのマイグレーション、そして段階的に移行を進めるための方法の検討という3つをタスクとして整理しました。
ここで少し、移行時に直面したMuddyな、つまりやや込み入った話をいくつか共有させてください。大きく3点ありました。
1つ目は、React Flow用にコンポーネントを再実装する必要があったという点です。AI Workerではすでに複数の種類のノードを提供しており、それらをすべて手作業で作り直すのは非常に手間がかかりました。さらに、それまで実装されていたノードのコンポーネントがRete.jsとかなり強く結合していたため、それを切り離して再構築するのが大変だったという課題がありました。
2つ目は、ダウンタイムゼロでのデータマイグレーションが必要だったことです。Rete.jsとReact Flowでは、ノードの構造、すなわちストラクチャが異なっており、それぞれの形式を変換してからデータベースに保存し直す必要がありました。この処理をユーザーに影響を与えずに進める必要があり、難易度の高いポイントでした。
3つ目は、段階的な移行についてです。AI Workerにおいてワークフロー機能は非常に重要なコア機能であり、多くのユーザーに利用されていました。そのため、React Flow版をいきなりすべてのユーザーに解放するのはリスクが高く、段階的に移行を進めていく必要がありました。この点も、移行を進める上で慎重に計画を立てた理由のひとつです。
まずは、UIの再実装についてです。もともとRete.jsでは、rete-react-pluginのプリセットを使ってUIを構築していたため、まずはそれらを取り外し、React Flow用のコンポーネントとして一から作り直す必要がありました。
ただ、ノードごとにコンポーネントのUIが多く存在していたため、手作業でひとつひとつ作り直すのは非常に手間がかかる状況でした。そうした中で、弊社のエンジニアの多くが使用している「Cursor」という開発支援エディタを活用し、私自身もそのCursorのAgent Modeという機能を使って、React Flow用のノードコンポーネントの実装をスムーズに進めることができました。
右側のキャプチャは、実際にCursorのAgent Modeを使って依頼した例になります。ここで登場する「@reactflow」や「@react」といった記述は、Cursorの@docsという機能を使ったものです。これは、React Flowの公式ドキュメントを参照して、そこから仕様やexampleを取得したうえで、コンテキストとしてAgent Modeに与えることができる仕組みです。これによって、ドキュメントに基づいた正確なコンポーネントを素早く生成してもらうことができました。
また、AI Workerではモノレポ構成を採用しており、AI Worker以外のコードも同じリポジトリ内に存在しています。そこで、「@Files」という形で、対象となる既存のコンポーネントが含まれているディレクトリを明示的に指定し、「このディレクトリ内のファイルをベースに変更してほしい」といった依頼も行っていました。こうした工夫により、UIの再実装作業はかなり効率的に進めることができました。
次に、データのマイグレーションについてです。もともとRete.jsでは、ワークフローの構造をJSON形式で出力し、そのままデータベースに格納していました。今回、React Flowへの移行にあたっては、このRete.jsの構造をReact Flowに適した構造へ変換する必要があったため、マイグレーション用のスクリプトを作成しました。
マイグレーション処理はTypeScriptで実装し、移行前の段階であらかじめこの変換スクリプトをリリースしておくことで、React Flowへの移行後にすぐ最新の状態で構造が適用できるようにしました。具体的には、フロントエンドでReact Flowを表示する直前に、Rete.js形式のデータをReact Flow形式に変換する処理を入れ、スムーズに表示できるようにしています。
これにより、既存データを一括で変換するのではなく、ユーザーの操作タイミングに合わせてリアルタイムに変換できるような設計にしておき、移行による影響を最小限に抑えるようにしました。
最後に、移行計画における「段階的に移行する手段」についてです。先ほどもお話しした通り、一気にすべてのユーザーに新しいUIを公開するのはリスクが大きかったため、Feature Flag ManagementサービスであるDevCycleを活用し、段階的に移行を進める方針に決めました。
最初は私たち開発チームのエンジニアのみが新しいReact FlowベースのワークフローUIを利用できる状態にし、そこから特定のテナント、そして最終的にはすべてのテナントへと段階を踏んで徐々に開放していきました。
移行後についてですが、当初は約1ヶ月を予定していたものの、実際には約2週間で移行を無事に完了することができました。
移行後に感じた良かった点としては、まずミニマップ機能を非常に簡単に追加できたことがあります。スライド上ではやや見えにくいかもしれませんが、画面右下にミニマップが表示されていて、これはコンポーネントを宣言するだけで実装できたものでした。
また、ピンチズームやマウスホイールによる操作も、以前Rete.jsで課題となっていた部分と同じような操作を行っても、React Flowではかなりスムーズに動作するようになりました。
開発体験の観点でも、たとえばonNodeChangeやonEdgeChangeといったコールバックが豊富に用意されており、非常に便利に感じました。また、exampleが豊富でカスタマイズ性も高く、コンポーネントの作成や機能追加も柔軟に対応できるようになった点も、React Flowに移行して良かったと感じる大きなポイントでした。
はい、最後に今回のまとめをさせていただきます。
AI Workerというプロダクトにおいて、主要機能であるワークフローのライブラリをReact Flowへと移行しました。移行にあたっては、CursorのAgent ModeやDevCycleのFeature Flagを活用し、UI再実装の工数削減や段階的なリリースの実現につなげることができました。
ユーザー体験の面では、ピンチズームやマウスホイールによるズーム操作の精度を改善し、より直感的で快適な操作性を提供できるようになりました。また、開発者体験の面では、React Flowが提供するカスタムフックや豊富なコンポーネントのexampleを活用することで、実装からリリースまでをスムーズに進めることができました。
以上で発表を終わります。ご清聴ありがとうございました。