タグアーカイブ React

React2Shellに学ぶReact Flightプロトコルの脆弱性と防御策

React2Shellに学ぶReact Flightプロトコルの脆弱性と防御策

WordPressをヘッドレスCMSとしてReactやNext.jsでフロントエンドを構築するプロジェクトが増えている。その中核となるReact Server Components(RSC)では、サーバーとクライアントの通信にFlightと呼ばれる独自のストリーミングプロトコルが使われる。しかし2025年12月、このFlightプロトコルにCVSS 10.0のリモートコード実行の脆弱性(CVE-2025-55182、通称React2Shell)が発見され、大きな話題となった。

本記事では、Flightプロトコルの仕組みと、なぜこれほど深刻な攻撃が可能になるのかを解説する。さらに、React2Shellの詳細な攻撃手法と、WordPressのヘッドレス構成でもすぐに実践できる防御策をランキング形式で紹介する。

Flightプロトコルとは何か(仕組みと危険性)

Flightプロトコルとは何か(仕組みと危険性)

React Server Componentsがブラウザに送るのはHTMLでもJSONでもない。サーバーコンポーネントがレンダリングされると、text/x-componentというContent-Typeで行区切りのテキストストリームが流れる。このフォーマットをFlightと呼ぶ。各行は「行ID:タグ+ペイロード」の形式で、Reactランタイムがストリームを読みながらクライアント側のUIを再構築する。

例えば、次のような単純なペイロードを見てほしい。行1はIタグでクライアントコンポーネントの読み込みを指示し、行2はJタグで仮想DOMを組み立てる。行0のDはサーバー側の実行コンテキストだ。これだけでも複数の役割と参照が絡み合っていることがわかる。

通常のFlightペイロード(Before)
行0 D{“name”:”RootLayout”}
行1 I[“./ClientComp.js”, …]
行2 J[“$”,”article”,null,{“children”:”$1″}]
※ 行2の “$1” が行1のコンポーネントへの参照
攻撃者が細工したペイロード(After)
行1 {“malicious”:{“__proto__”:null},”hijack”:function(){…}}
行2 J[“$”,”article”,null,{“children”:”$1:__proto__:constructor:constructor”}]
※ “$1:__proto__:constructor:constructor” がプロトタイプ汚染を誘発

Flightプロトコルは、単なるJSON形式ではない。$接頭辞によってクライアントサイドで実行するコードやモジュール読み込み、サーバーアクションのRPC呼び出しを再構成する。この仕組みが強力であるほど、入力が攻撃者に操作された場合の危険性も増す。

具体的には、$Fは呼び出し可能なサーバー関数を表し、$Lは遅延読み込みコンポーネント、$@は内部的なPromiseラッパーへの参照を返す。中でも$:$1:user:nameのようにコロン区切りでオブジェクトのプロパティをたどる機能で、もしパスに__proto__constructorが含まれるとプロトタイプチェーンを遡ることになる。これが設計上の重大な問題の始まりだ。

React2Shell(CVE-2025-55182)の攻撃メカニズム

React2Shell(CVE-2025-55182)の攻撃メカニズム

2025年12月に公表されたCVE-2025-55182は、Flightのデシリアライゼーション処理に潜むCVSS 10.0のリモートコード実行の脆弱性だ。認証不要の1回のHTTPリクエストでサーバーにシェルアクセスを許す。CISAは直ちに「悪用が確認された脆弱性カタログ」に追加し、北朝鮮の国家支援ハッカーが数時間以内に攻撃を開始したとSysdigが報告している。

根本原因はgetOutlinedModel関数にある。この関数は$1:user:nameのような参照を解決する際、コロンでパスを分割し、単純にparentObject[segment]でプロパティアクセスを繰り返す。hasOwnPropertyによるチェックは一切なかった。

STEP 1 $1:__proto__:constructor:constructor で Function コンストラクタに到達
STEP 2 $@0 で内部 Chunk オブジェクトを取得(生のラッパー)
STEP 3 Chunk の .then をハイジャックし Thenable 化
STEP 4 _response._formData.get を Function に差し替え
STEP 5 $B0 でブロブハンドラを発火 → 任意コード実行

このガジェットチェーンは、Flightの持つ機能を悪用し、1回のHTTPリクエストでサーバーを乗っ取る。ログインも認証も不要だ。最終的に攻撃者はNode.jsプロセスの権限で任意のコマンドを実行できる。

SYSDIGの調査では、この脆弱性を利用した「EtherRAT」と呼ばれるファイルレス型インプラントが、イーサリアムブロックチェーンをC2通信に使う「EtherHiding」手法で展開され、テイクダウンが極めて困難だった。またPalo AltoのUnit 42は、感染Linuxシステムで正規のカーネルスワップデーモン(kswapd0)に偽装するバックドア「KSwapDoor」を確認している。

修正パッチの内容と限界

修正パッチの内容と限界

Reactチームはモジュールロード時にObject.prototype.hasOwnPropertyをキャッシュし、以後すべてのプロパティチェックでこれを使うパッチを適用した。これにより、攻撃者が__proto__を経由する試みはブロックされる。修正はReact 19.0.1、19.1.2、19.2.1に含まれており、既知のガジェットチェーンを完全に無効化する。

しかし、パッチはプロパティ探索のモデルそのものは維持している。$:プレフィックスは依然としてコロン区切りのパスを走査し、所有権を検証するようになっただけだ。設計上の根本問題は残っており、今後の新たなバイパスがこの領域から出てくる可能性は否定できない。Smashing Magazineの筆者も「プロパティ探索をネットワークプロトコルに露出させたこと自体が設計ミスだ」と指摘している。

実践的な防御策(影響度順ランキング)

Reactのパッチに頼るだけでなく、アプリケーションレベルで複数層の対策を取ることが肝心だ。以下は、実際の攻撃を防ぐ効果が高い順に並べた防御策である。

1. Server Actionの厳格な入力バリデーション(Zod、Valibot)

最も即効性があるのは、すべてのサーバーアクションの先頭でスキーマバリデーションを行うことだ。Flightデシリアライザはアプリケーションロジックより先に生データを処理するため、バリデーションが唯一の事前防御になる。ZodやValibotを使い、型、文字列長、数値範囲、列挙値を厳密にチェックする。

特に注意すべきは、引数を分割代入する前にバリデーションを済ませることだ。分割代入の時点で未検証のオブジェクトにアクセスしているため、まさにその操作が悪用される可能性がある。またエラーハンドリングでは.safeParse()を使い、内部情報が漏れないようにする。

2. server-only パッケージ

データベース接続やAPIキーを含むファイルの先頭にimport "server-only"を入れるだけで、クライアントコンポーネントへのトランスパイル時エラーを発生させる。バレルファイル(複数のエクスポートをまとめるindex.ts)による意図しないエクスポートには細心の注意が必要だが、コードの境界を強制するシンプルで強力な手段だ。

3. CSRF対策の強化

Next.js 16.1.7で修正される前に発見されたCVE-2026-27978は、サンドボックス化されたiframeから送られるOrigin: nullをNext.jsがクロスオリジンとみなさないバグだった。対策として、セッションクッキーにSameSite=Strictを設定し、重要な操作には独自のCSRFトークンを実装する。またexperimental.serverActions.allowedOrigins'null'を絶対に追加しないこと。これだけでCSRFバイパスを再び開放してしまう。

4. パッチ適用バージョンの維持

React2ShellのRCE修正は19.0.1、19.1.2、19.2.1で行われたが、それ以降にDoSの脆弱性(CVE-2025-55184、CVE-2025-67779、CVE-2026-23864)も複数回修正されている。最新のセキュリティリリース(19.0.4以上、19.1.5以上、19.2.4以上)に追随することが不可欠だ。

5. Taint API(実験的)

experimental_taintObjectReferencetaintUniqueValueは、オブジェクトや文字列が誤ってクライアントにシリアライズされるのを防ぐ。しかしこれはオブジェクト参照を追跡する仕組みであり、スプレッド構文や個別プロパティの受け渡しで追跡が途切れる。あくまで開発時のフェイルセーフとして捉え、セキュリティ境界にはしない方がよい。

6. WAF(Web Application Firewall)

WAFはNext-Actionヘッダー付きのPOSTリクエストを検査し、__proto__constructor:constructorを含むペイロードをブロックするルールを追加できる。しかし、攻撃者が検査バッファを超えるパディングを前につけることで簡単にすり抜けられるため、あくまでノイズ低減層と割り切るべきだ。

React2Shell以降の脆弱性と今後の課題

React2Shell以降の脆弱性と今後の課題

React2Shellの修正後も、Flightのデシリアライゼーション面では複数のCVEが報告されている。特に、入れ子になったPromiseによる無限再帰(CVE-2025-55184)とその不完全な修正(CVE-2025-67779)、zipbomb的なメモリ枯渇を起こすDoS(CVE-2026-23864)は、デシリアライザーのパッチがいかに難しいかを示している。また、サーバー関数が引数を文字列化するだけでソースコードが流出するCVE-2025-55183は、デバッグ用途のJSON.stringifyが裏目に出る好例だ。

さらに、パッチでは塞がれていない構造的なリスクも残る。Flightストリームは平文で、CDN改ざんやキャッシュポイズニングによる中間者攻撃を受けやすい。攻撃者が行単位で$I$Fを書き換えれば、任意のモジュールをロードさせたり、隠しRPCエンドポイントを埋め込んだりできる。また、server-reference-manifest.jsonが公開されていると、すべてのサーバーアクションIDが漏洩し、IDOR攻撃に直結する。暗号化されたクロージャ引数を複製するために静的キーが使われている場合も、ファイル読み取り権限さえ奪取できれば改ざん可能だ。

根本的には、ネットワーク越しにプロパティ探索や実行可能な参照を再構成させる設計が、今後も攻撃者に利用される可能性をはらんでいる。Reactチームは既知のガジェットを塞いだが、これまでの歴史が示すように、シリアライゼーションフレームワークの脆弱性は根絶が難しい。

この記事のポイント

  • React FlightプロトコルはJSONの延長ではなく、$接頭辞でクライアント側の実行可能コードやモジュール読み込みを指示する「振る舞いのシリアライゼーション」である。
  • CVSS 10.0のReact2Shellは、プロトタイプ汚染とChunkオブジェクト操作を組み合わせ、認証なしでRCEを達成した。
  • パッチはhasOwnPropertyチェックを追加したが、プロパティ探索の根本設計は変わっておらず、新たな攻撃が生まれる余地がある。
  • 実務では、Server Actionの先頭で必ずスキーマバリデーションを実施し、server-onlyでコード境界を強制し、CSRF対策を二重化することが最も効果的な防御となる。
  • Taint APIやWAFは補助的な防御に過ぎず、過信せずに多層防御を組み立てる必要がある。
Next.js 2026年5月セキュリティリリースの全容、13件の脆弱性を修正

Next.js 2026年5月セキュリティリリースの全容、13件の脆弱性を修正

Next.jsの開発元であるVercelは2026年5月7日、調整済みセキュリティリリースを公開した。React Server Componentsの上流脆弱性(CVE-2026-23870)への対応を含む、合計13件の脆弱性が修正されている。フレームワークを利用するすべての開発者にとって、即時のアップデートが強く推奨される内容だ。

今回のリリースで対処された問題は、サービス拒否(DoS)攻撃、ミドルウェアやプロキシのバイパス、サーバーサイドリクエストフォージェリ(SSRF)、キャッシュポイズニング、クロスサイトスクリプティング(XSS)と多岐にわたる。ただちにパッチを適用しなければ、本番環境が深刻な攻撃に晒されるリスクがある。

アップデートの概要と影響範囲

アップデートの概要と影響範囲

今回のセキュリティリリースはNext.js本体に加え、その根幹をなすReactの特定パッケージにも修正が及ぶ。Vercelの発表によれば、13件の脆弱性アドバイザリが一挙に解決された形だ。アドバイザリの内訳を見ると、攻撃の種類によって影響を受ける機能や設定が明確に分かれている。自社のプロジェクトがどのカテゴリに該当するかを把握することが、迅速な対応への第一歩となる。

Before(パッチ未適用)
⚠ 複数の脆弱性が存在
ミドルウェア認証のバイパス
React Server Components の DoS
キャッシュポイズニング
SSRF の潜在的リスク
After(パッチ適用後)
✓ 13件の脆弱性を一括修正
認証機能が正常に動作
DoS 攻撃から保護
キャッシュへの不正注入を防止
SSRF の脆弱性を遮断

13件の脆弱性が存在する「Before」の状態と、アップデートによってそれらがすべて解決された「After」の状態を比較したイメージだ。リリースによってセキュリティ上のリスクが一掃されることがわかる。

修正対象となったReactとNext.jsのバージョン

影響を受けるバージョンを把握し、修正済みの安全なバージョンへ移行する必要がある。今回のリリースで修正が提供されたバージョンは以下の通りだ。

まず、React本体については、19.0.6、19.1.7、19.2.6の3つのパッチがリリースされた。これらのバージョンでは、サーバーコンポーネントの通信用パッケージである react-server-dom-parcel、react-server-dom-webpack、react-server-dom-turbopack の各パッケージに修正が含まれている。

Next.jsをフレームワークとして利用している場合、これらのReactパッケージはNext.jsのバージョンにバンドルされている。そのため、開発者はNext.js本体を最新のパッチバージョンに更新することで、React側の修正も同時に適用できる。個別にReactパッケージを管理しているプロジェクトは、それらも忘れずにアップデートする必要がある。

各脆弱性の詳細とリスク評価

各脆弱性の詳細とリスク評価

ここからは、発表されたアドバイザリを深刻度別に整理し、その技術的な背景をひも解いていく。影響を受けるコンポーネントと、攻撃が成立するシナリオを正しく理解することが、開発者としての適切なリスク評価に繋がる。

ミドルウェアとプロキシのバイパス

認証や認可のロジックを middleware.js や proxy.js に依存しているアプリケーションが、致命的な影響を受ける可能性がある。2件の「高」深刻度アドバイザリがこれに該当する。

1件目はApp Routerにおける segment-prefetch のバイパスで、過去の修正が不完全だったための再発フォローアップだ。2件目はPages Routerのi18n機能において、デフォルトロケールのパスがプロキシ認証を迂回してしまう問題である。多言語サイトをPages Routerで運用しており、middlewareでアクセス制御を行っている環境は特に注意が必要だ。

サービス拒否(DoS)攻撃

サーバーのリソースを枯渇させ、正規のユーザーがサイトにアクセスできなくなるDoS攻撃に関する脆弱性が3件報告されている。これらはすべて、Server Functions、Partial Prerendering(PPR)のCache Components、もしくは画像最適化APIの利用が条件となる。

最もクリティカルなものは、React Server Componentsの上流脆弱性(CVE-2026-23870)を突いた攻撃だ。Vercelの発表によると、この脆弱性によりリモートからのDoS攻撃が成立する。また、Cache Componentsを使用するアプリケーションにおける「接続数の枯渇」を引き起こす脆弱性も「高」深刻度と評価されている。画像最適化APIを経由したDoSは「中」深刻度だが、無視できるものではない。

攻撃の流れ(イメージ)
攻撃者 悪意あるリクエスト送信 脆弱なNext.jsサーバー リソース枯渇 正規ユーザーがアクセス不可
攻撃者  攻撃の流れ  影響を受ける主体

この図は、悪意あるリクエストによってサーバーのリソースが消費され、本来のサービス提供が妨害されるDoS攻撃の基本的な流れを示している。Cache Componentsの脆弱性は、この「リソース枯渇」の段階を特に加速させる危険性がある。

サーバーサイドリクエストフォージェリ(SSRF)

SSRFは、攻撃者がサーバーを踏み台にして内部ネットワークへのリクエストを強制させる攻撃手法だ。今回の脆弱性は、WebSocketへのアップグレードリクエストを処理するアプリケーションが影響を受ける。

この種の攻撃が成功すると、攻撃者は本来アクセスできない内部のメタデータサービスやデータベースと通信できるようになる。クラウド環境(AWSやGCPなど)で稼働しているNext.jsアプリケーションは、特に深刻な被害に繋がる可能性があるため、迅速な対応が求められる。

キャッシュポイズニングとクロスサイトスクリプティング(XSS)

React Server Componentのレスポンスの前にキャッシュ層を配置しているアプリケーションは、キャッシュポイズニングのリスクに晒される。これは、攻撃者が悪意あるレスポンスをキャッシュサーバーに記憶させ、他のユーザーにその不正なコンテンツを配信させる攻撃だ。

XSSに関しては、App RouterでCSP(コンテンツセキュリティポリシー)のnonceを利用しているケース、そして外部からの信頼できない入力を消費する beforeInteractive スクリプトが影響を受ける。これらの設定は比較的高度なカスタマイズで使われるが、該当する場合はすぐに対処しなければ攻撃者によるスクリプト実行を許してしまう。

即時アップデートの必要性と対応手順

即時アップデートの必要性と対応手順

Vercelは今回のリリースに際し、新たなWAF(Web Application Firewall)ルールを展開していないと明言している。つまり、これらの脆弱性はWAFレベルで確実にブロックすることができず、パッチ適用が唯一の完全な緩和策となる。

アップデート手順の基本

まず、プロジェクトのNext.jsとReactのバージョンを確認する。package.jsonに記載されているバージョンが、今回の修正対象より古い場合は即座にアップデートを実行する。一般的な手順は以下の通りだ。

# Next.jsのアップデート
npm install next@latest

# 関連するReactパッケージも最新に
npm install react@latest react-dom@latest

yarnやpnpmなど、他のパッケージマネージャーを利用している場合も、同等のコマンドで問題ない。パッケージを更新した後は、必ずビルドとテストを実行し、アプリケーションが正常に動作することを確認してほしい。

本番環境でのリスク管理

今回のセキュリティリリースには、破壊的な変更は含まれていない。そのため、動作検証は必要だが、適用を躊躇する技術的理由はほぼない。重要なのは「スピード」だ。

とりわけ、middlewareで認証を実装しているサイト、WebSocketを処理するリアルタイムアプリケーション、そしてPPRやCache Componentsを採用しているパフォーマンス重視のサイトは、緊急度が極めて高い。Vercelの発表でも「all affected users should upgrade immediately(影響を受けるすべてのユーザーは直ちにアップグレードすべき)」と強い表現で呼びかけている。

今回のリリースが示すNext.jsセキュリティの潮流

今回のリリースが示すNext.jsセキュリティの潮流

一見すると大規模な脆弱性の一括修正はネガティブな出来事に思える。しかし、セキュリティの観点からは、むしろフレームワークの成熟度を示すポジティブなシグナルと捉えるべきだ。

まず、対策が「調整済みセキュリティリリース」として計画的に実施されている点が重要だ。これは、VercelとMeta(React)のチームが発見された問題を共有し、エコシステム全体で同時に対処する体制が整っていることを意味する。大規模なOSSプロジェクトでは、このような「調整済み開示」のプロセスがセキュリティ品質の生命線となる。

次に、脆弱性の範囲が「Server Components」「ミドルウェア」「エッジキャッシュ」「画像最適化API」といった、Next.jsの比較的新しい機能や高度な機能に集中している事実に注目したい。これは、攻撃者の標的が、従来のシンプルなWebアプリケーションから、エッジとサーバーを高度に組み合わせたモダンなアーキテクチャへとシフトしている証左だ。

SSRやエッジファンクションの利用が一般化するにつれ、開発者は「新しい機能がもたらす利便性」と「新たな攻撃表面が生まれるリスク」のバランスを常に意識する必要がある。便利なAPIほど、その裏側で何が起きているのかを深く理解することが、これからのフロントエンド開発者には不可欠だ。

この記事のポイント

  • Next.js 2026年5月セキュリティリリースは、ReactのCVE-2026-23870を含む13件の脆弱性を修正する
  • 影響範囲はDoS、ミドルウェアバイパス、SSRF、キャッシュポイズニング、XSSと多岐にわたる
  • WAFでは防げない脆弱性群のため、Next.jsとReact関連パッケージの即時アップデートが唯一の対策
  • とくに認証機能やServer Components系の新機能を使うプロジェクトは緊急度が高い
  • 調整済みリリースの実施は、Next.jsエコシステムのセキュリティ成熟度を示すポジティブな側面でもある
React/Next.jsで動的フォームを構築する2つの手法——RHF+ZodとSurveyJSの比較

React/Next.jsで動的フォームを構築する2つの手法——RHF+ZodとSurveyJSの比較

Reactアプリケーションにおいて、フォームの実装は避けて通れない課題だ。ログイン画面のような単純なものから、条件分岐が複雑に絡み合う多ステップの入力フォームまで、その難易度は多岐にわたる。

多くの開発者が「フォームはコンポーネントとして構築すべきだ」という共通のメンタルモデルを持っている。React Hook Form(RHF)とZodを組み合わせる手法は、現在のReactエコシステムにおける標準的な選択肢となっている。

しかし、フォームが「UI」の枠を超え、複雑な「ルールエンジン」へと変貌したとき、このモデルは限界を迎える。本記事では、コンポーネント駆動とスキーマ駆動という2つの異なるアプローチを比較し、プロジェクトに最適な手法を選択するための基準を提示する。

コンポーネント駆動アプローチ:React Hook FormとZodの組み合わせ

コンポーネント駆動アプローチ:React Hook FormとZodの組み合わせ

React Hook Form(RHF)は、非制御コンポーネントを活用して再レンダリングを最小限に抑えるライブラリだ。これにスキーマバリデーションライブラリであるZodを組み合わせることで、型安全で直感的なフォーム実装が可能になる。

Zodによるバリデーションスキーマの定義

コンポーネント駆動開発では、まずZodでデータの形状(Shape)を定義する。単純な必須チェックだけでなく、特定の条件に基づいた相関バリデーションも記述できる。

import { z } from "zod";

export const formSchema = z.object({
  firstName: z.string().min(1, "必須項目です"),
  email: z.string().email("無効なメール形式です"),
  price: z.number().min(0),
  quantity: z.number().min(1),
  taxRate: z.number(),
  hasAccount: z.enum(["Yes", "No"]),
  username: z.string().optional(),
  password: z.string().optional(),
  satisfaction: z.number().min(1).max(5),
  positiveFeedback: z.string().optional(),
  improvementFeedback: z.string().optional(),
}).superRefine((data, ctx) => {
  if (data.hasAccount === "Yes") {
    if (!data.username) {
      ctx.addIssue({ code: "custom", path: ["username"], message: "必須項目です" });
    }
    if (!data.password || data.password.length < 6) {
      ctx.addIssue({ code: "custom", path: ["password"], message: "6文字以上必要です" });
    }
  }
});

export type FormData = z.infer<typeof formSchema>;

このコードでは、`superRefine` を使用して「アカウントあり(Yes)」の場合にのみユーザー名とパスワードを必須にするロジックを実装している。Zodのスキーマはデータの構造を定義するものであり、どのフィールドがどのタイミングで表示されるかといった「表示ロジック」までは関与しない。

RHFによるフォームの実装と課題

RHFを用いた実装では、フォームの表示状態やステップの管理をReactの `useState` や `useWatch` で行う。

const { register, control, handleSubmit } = useForm<FormData>({
  resolver: zodResolver(formSchema),
});

const hasAccount = useWatch({ control, name: "hasAccount" });
const price = useWatch({ control, name: "price" });
const quantity = useWatch({ control, name: "quantity" });

const subtotal = useMemo(() => (price ?? 0) * (quantity ?? 1), [price, quantity]);

この手法の利点は、Reactの標準的な作法に従っているため、学習コストが低く自由度が高い点にある。一方で、条件分岐が増えるにつれてJSX内にインラインの条件式が散在し、ビジネスロジックがUIコードと密結合になるという課題がある。

スキーマ駆動アプローチ:SurveyJSによるデータ中心の実装

スキーマ駆動アプローチ:SurveyJSによるデータ中心の実装

スキーマ駆動アプローチでは、フォームをコンポーネントの集合体ではなく、一つの「データ(JSONスキーマ)」として扱う。SurveyJSはこの手法を代表するライブラリであり、フォームの構造、バリデーション、表示ロジック、計算式をすべてJSON内に集約できる。

JSONスキーマによるロジックの集約

SurveyJSでは、JavaScriptのコードではなく、宣言的なJSONオブジェクトでフォームを定義する。

const surveySchema = {
  pages: [
    {
      name: "order",
      elements: [
        { type: "text", name: "price", inputType: "number" },
        { type: "text", name: "quantity", inputType: "number" },
        {
          type: "expression",
          name: "subtotal",
          expression: "{price} * {quantity}"
        }
      ]
    },
    {
      name: "account",
      elements: [
        { type: "radiogroup", name: "hasAccount", choices: ["Yes", "No"] },
        {
          type: "text",
          name: "username",
          visibleIf: "{hasAccount} = 'Yes'",
          isRequired: true
        }
      ]
    }
  ]
};

ここで注目すべきは `visibleIf` や `expression` というプロパティだ。これらはSurveyJSのランタイムエンジンによって評価され、Reactのステート管理を介さずに動的な表示切り替えや計算結果の表示を可能にする。

Reactコンポーネントの役割の変化

SurveyJSを採用すると、Reactコンポーネントの役割は「スキーマを読み込んで描画するだけ」という極めてシンプルなものになる。

import { Model } from "survey-core";
import { Survey } from "survey-react-ui";

export function SurveyForm() {
  const [model] = useState(() => new Model(surveySchema));

  model.onComplete.add((sender) => {
    console.log(sender.data); // 送信データ
  });

  return <Survey model={model} />;
}

ビジネスロジックがReactから切り離されるため、エンジニア以外の担当者がJSONを書き換えるだけでフォームの挙動を調整できる。これは、要件変更が頻繁に発生するエンタープライズ向けのシステムにおいて、運用上の大きなメリットとなる。

技術的なトレードオフと選択基準

技術的なトレードオフと選択基準

どちらのアプローチが優れているかは、プロジェクトの性質に依存する。ここでは、開発効率と保守性の観点から両者を比較する。

コンポーネント駆動(RHF+Zod)が適しているケース

RHF+Zodのスタックは、フォームが単純なCRUD(作成・読み取り・更新・削除)操作に限定されている場合に最適だ。

  • UIの微調整や独自のアニメーションを多用する場合
  • フォームのステップ数が少なく、条件分岐が単純な場合
  • エンジニアがすべての挙動をコードで管理し、外部からロジックを注入する必要がない場合

このアプローチでは、TypeScriptの恩恵を最大限に受けられる。型定義が明確であるため、大規模なリファクタリングにも強いという特性がある。

スキーマ駆動(SurveyJS)が適しているケース

一方で、SurveyJSのようなスキーマ駆動が威力を発揮するのは、フォーム自体が「ビジネスルール」を体現している場合だ。

  • 法規制や業務フローの変化により、入力項目や条件が頻繁に変わる場合
  • 10ステップを超えるような長大な多ステップフォームを構築する場合
  • フォームの定義をデータベースに保存し、複数のプラットフォーム(Web、モバイルなど)で共有する場合

スキーマ駆動は、ロジックの「可視化」に優れている。JSONを見ればどの項目がどの条件で表示されるかが一目瞭然であり、コードを追う必要がない。

実務における運用上のメリットと分析

実務における運用上のメリットと分析

Web制作会社の視点では、保守コストの削減が最も重要な関心事となる。コンポーネント駆動で構築された複雑なフォームは、半年後の仕様変更時に「どの `useWatch` がどこに影響しているか」を解読する作業から始めなければならない。

スキーマ駆動を採用した場合、ロジックが中央集権的に管理されているため、修正箇所が限定される。また、SurveyJSにはGUIのビジュアルエディタも存在するため、非エンジニアであるディレクターやクライアントが直接フォームを編集し、エンジニアは生成されたJSONを組み込むだけという分業体制も構築可能だ。

ただし、SurveyJSのような重量級のライブラリは、バンドルサイズが増大する傾向にある。パフォーマンスが最優先されるBtoCのランディングページなどでは、RHFを用いた軽量な実装が選好されるだろう。

この記事のポイント

  • RHF+Zodは、UIの自由度が高く、小〜中規模のCRUDフォームに最適である。
  • SurveyJSは、ビジネスロジックをJSONに集約し、複雑な条件分岐を持つフォームの保守性を高める。
  • 「フォームを消したときに失われるものがUIならRHF、ロジックならSurveyJS」という判断基準が有効だ。
  • 運用フェーズでの要件変更の頻度を予測し、適切な抽象化レベルを選択することが重要である。

出典

  • Smashing Magazine “Building Dynamic Forms In React And Next.js”(2026年3月10日)