問題 / イシュー
2025/08/27
Framer コードの境界: React エラー処理
FramerはReactベースのウェブサイト作成ツールで、エラーが発生したコンポーネントのみを非表示にし、サイト全体の正常動作を維持する「コード境界」機能を実装してユーザー体験を向上させます。

投稿者

による翻訳
目次
目次
Framerとカスタムコードのジレンマ
Framerは基本的にノーコードのウェブサイト作成ツールです。しかし、全てのサイトは実際にはReactで作成されたアプリであり、ユーザーはいつでもカスタムReactコンポーネントやオーバーライドを追加することができます。
ここで問題が発生します。Reactアプリでコンポーネントがエラーを起こしたらどうなるのでしょうか? アプリ全体の構造が壊れて、画面には白い空白のページが残るだけになります。

Framerサイトも以前はこのように動作していました。カスタムコードが一つでも不正だった場合、サイト全体が停止してしまっていたのです。これを修正するために、この冬から新しいアプローチを始めました。目標は、カスタムコードが壊れても、そのコンポーネントだけを隠し、他のサイトは正常に動作するようにすることでした。
では、本格的にReactエラーのハンドリング方法を見ていきましょう。
Level 1: エラーの範囲(Error Boundaries)
表面的に言えば、Reactでエラー境界を設けることはこのようなケースに備えるために存在します。各カスタムコードコンポーネントのエラー範囲を境界で囲み → エラーを検出し → レンダリングし null(空の値) → 完了する、という流れでエラーが発生した部分を境界で囲んで隠すことがエラーバウンダリーの役割です。
このように各カスタムコードコンポーネントを囲むと、エラーが発生してもnullだけをレンダリングすれば、残りのコードは安全でしょう。しかし、ここにも問題があります。

Reactエラーバウンダリーはクライアントでのみ動作します。サーバーサイドレンダリング中には完全に無視される部分です。したがってサーバーでレンダリングが失敗した場合、ページ自体が最適化されず、パフォーマンスも低下してしまいます。
Level 2: Suspense
サーバーレンダリングでは、Reactはエラーが発生した境界を無視します。代わりにサーバーでエラーが発生すると、Reactは最も近くの<Suspense>バウンダリーを探してそれを代替するコードをレンダリングします。
したがって、エラーがあるコンポーネントを隠すには、エラー境界がクライアント用とサーバー用の2つが必要です。
しかし、すべてのコードに<Suspense>を囲むのは難しいかもしれません。サーバーでエラーをキャッチする以外にも様々な作業を実行するためです。
Level 3: Suspenseの多様な動作
<Suspense>は単にエラーをキャッチする機能があるだけでなく、同時に複数の動作を行います。<Suspense>は非常に基本的な機能であり、2025年時点で大まかに以下の機能を提供します。
<Suspense>サーバー:何かが中断した場合、代替コードをレンダリング (
stream.allReadyでスキップ可能)エラー発生時に代替コードをレンダリング
<Suspense>クライアント:何かが中断した場合、代替コードをレンダリング (
startTransition()でスキップ可能)
ではレンダリング中にユーザーのコードが中断された場合はどうなるのでしょうか?
サーバーでは、stream.allReadyを使ってコードが中断された状態で復帰するのを待つことができます。しかし、クライアントでは状況が異なります。コンポーネントが中断されると、<ServerErrorBoundary>内の<Suspense>が代替コードとして構成されたnullを一時的にレンダリングします。つまり、画面上でそのコンポーネントが消えて現れるというフラッシュが生じるわけです。
もちろん、コンポーネントを囲むコードがstartTransition内で実行されればこの現象を避けることができますが、ユーザーコードまで制御することはできません。その結果、ユーザーはデータを取得している間にコンポーネントが一瞬消え再び現れるというUXの観点から良くない体験をすることになるのです。
この問題はどのように解決できるでしょうか?
<ServerErrorBoundary>をクライアントで一切レンダリングしない方法を試してみることができます。しかし、これを行うとハイドレーションの不一致が発生します。
その理由は、<ServerErrorBoundary>が内部で<Suspense>をレンダリングするためです。<Suspense>自体は実際のDOM要素を生成しませんが、代わりに特殊なコメント(<!--$-->など)を出力します。サーバーが生成したこのコメントをクライアントがハイドレーション中に正確に見つけて「同一」と確認する必要がありますが、クライアントで<ServerErrorBoundary>を除去するとそのコメントがなくなります。するとReactは「サーバーとクライアントが異なる」と判断してエラーを発生させるのです。

ではハイドレーションが始まる前に<Suspense>の特殊なコメントを削除してしまえばどうでしょうか? これも不可能です。ユーザーコードがサーバーではエラーによってnullをレンダリングしましたが、クライアントでは正常に動作(正しいJSXをレンダリングする時など)してしまうとサーバーとクライアントの結果が異なってしまうのです。これによってハイドレーションの不一致が発生し、最終的にはルート全体が再マウントされる問題が発生します。
では<Suspense>のfallback内部で意図的に中止(suspend)させる場合どうでしょうか? この方法は効果的です、このルールがあるからです。「ある<Suspense>がfallback(代替要素)をレンダリングする場合/そのfallback自体が再び中止(suspend)すると、Reactはその<Suspense>境界を無視するということになります。」
つまり、fallbackが自ら中止するとその<Suspense>が存在しないかのように扱われるということです。
ドキュメントには親の<Suspense>の境界で有効化されるとされていますが、親の境界がない場合でもまるでバウンダリーがないかのように動作できるので、フラッシュなしで望んだ動作だけを維持できるのです。
もちろん、ドキュメント化されていない動作に依存するのはリスクがありますが、短期的にはかなり安定した解決策となるでしょうね。
Level 4: 外部コンポーネント
ここで一段階、複雑になっていきます。Framerではコードを直接書くだけでなく、他の人が書いたコードを再利用することも可能です。さらに、このコードは他の人が作ったノーコードUIの中にネストされることもあります。

これにより、実装がさらに複雑になる可能性があります。例えば、上記のようにユーザーが指定したコードのFormコンポーネント内にInputコードがあり、このInputにエラーが生じた場合どうすべきでしょうか?単純に境界が衝突する要素であるInputだけを隠すのが良いのでしょうか?

Framerの哲学上、Formはサイト作成者にとって一つの「不透明な単位」として見ることができます。自分のコンポーネントではないため内部を確認することはできません。したがって、境界が衝突した際にも内部の一部だけが壊れるのではなく、全てのFormが一緒に全体的に壊れるようにしなければなりません。 そうすることで、製作者が直感的に衝突状況を理解できるからです。
Level 5…N: 最後の難問
私たちが解決しなければならなかった最後の難問をご紹介します。
Framerはコードのコンポーネントだけでなくコードオーバーライドもサポートしているため、
Code Boundariesもこのケースまで全て考慮する必要がありました。多くのコードエラーは基本的に解読が難しいのですが、特にコードが縮小されている場合にはさらに困難です。このため、どのコンポーネントがエラーを起こしているのかを簡単に特定できるツールを新たに作成する必要がありました。

単にReactエラーバウンダリーを解決することから始めたが、サーバー・クライアント環境の違い、Suspenseの複雑な動作、外部コード再利用など多くの難問を解決した後、ようやくCode Boundariesをアップデートすることができました。
この記事はFramer公式ブログの「Rabbit hole of React error handling」を翻訳・加工したコンテンツです。




