読む時間

0

読む時間

0

問題 / イシュー

2025/08/27

Framer コードの境界: React エラー処理

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

フレーマー(Framer)におけるReactエラー処理方法を紹介するブログサムネイルで、カスタムコードのエラーを管理し、サイトの安定性を向上させる方法について説明しています。
フレーマー(Framer)におけるReactエラー処理方法を紹介するブログサムネイルで、カスタムコードのエラーを管理し、サイトの安定性を向上させる方法について説明しています。
フレーマー(Framer)におけるReactエラー処理方法を紹介するブログサムネイルで、カスタムコードのエラーを管理し、サイトの安定性を向上させる方法について説明しています。

目次

目次

Framerとカスタムコードのジレンマ

Framerは基本的にノーコードのウェブサイト作成ツールです。しかし、全てのサイトは実際にはReactで作成されたアプリであり、ユーザーはいつでもカスタムReactコンポーネントやオーバーライドを追加することができます。

ここで問題が発生します。Reactアプリでコンポーネントがエラーを起こしたらどうなるのでしょうか? アプリ全体の構造が壊れて、画面には白い空白のページが残るだけになります。

React 앱에서 컴포넌트가 에러를 일으킨 화면을 나타냅니다.

Framerサイトも以前はこのように動作していました。カスタムコードが一つでも不正だった場合、サイト全体が停止してしまっていたのです。これを修正するために、この冬から新しいアプローチを始めました。目標は、カスタムコードが壊れても、そのコンポーネントだけを隠し、他のサイトは正常に動作するようにすることでした。
では、本格的にReactエラーのハンドリング方法を見ていきましょう。

Level 1: エラーの範囲(Error Boundaries)

表面的に言えば、Reactでエラー境界を設けることはこのようなケースに備えるために存在します。各カスタムコードコンポーネントのエラー範囲を境界で囲み → エラーを検出し → レンダリングし null(空の値) → 完了する、という流れでエラーが発生した部分を境界で囲んで隠すことがエラーバウンダリーの役割です。

class ErrorBoundary extends React.Component {
  static getDerivedStateFromError(error) {
    return { hasError: true }
  }

  render() {
    return this.state.hasError ? null : this.props.children
  }
}

このように各カスタムコードコンポーネントを囲むと、エラーが発生してもnullだけをレンダリングすれば、残りのコードは安全でしょう。しかし、ここにも問題があります。

Base Domain에서 React 에러 바운더리를 설명합니다.

Reactエラーバウンダリーはクライアントでのみ動作します。サーバーサイドレンダリング中には完全に無視される部分です。したがってサーバーでレンダリングが失敗した場合、ページ自体が最適化されず、パフォーマンスも低下してしまいます。

Level 2: Suspense

サーバーレンダリングでは、Reactはエラーが発生した境界を無視します。代わりにサーバーでエラーが発生すると、Reactは最も近くの<Suspense>バウンダリーを探してそれを代替するコードをレンダリングします。

したがって、エラーがあるコンポーネントを隠すには、エラー境界がクライアント用とサーバー用の2つが必要です。

class ClientErrorBoundary extends React.Component {
  // 위와 동일
}

function ServerErrorBoundary({ children }) {
  return <Suspense fallback={null}>{children}</Suspense>
}

function ErrorBoundary({ children }) {
  return (
    <ServerErrorBoundary>
      <ClientErrorBoundary>
        {children}
      </ClientErrorBoundary>
    </ServerErrorBoundary>
  )
}

しかし、すべてのコードに<Suspense>を囲むのは難しいかもしれません。サーバーでエラーをキャッチする以外にも様々な作業を実行するためです。

Level 3: Suspenseの多様な動作

<Suspense>は単にエラーをキャッチする機能があるだけでなく、同時に複数の動作を行います。<Suspense>は非常に基本的な機能であり、2025年時点で大まかに以下の機能を提供します。

  • <Suspense>サーバー:

    1. 何かが中断した場合、代替コードをレンダリング (stream.allReadyでスキップ可能)

    2. エラー発生時に代替コードをレンダリング

  • <Suspense>クライアント:

    1. 何かが中断した場合、代替コードをレンダリング (startTransition()でスキップ可能)

    2. 選択的・並行的なハイドレーションをサポート

ではレンダリング中にユーザーのコードが中断された場合はどうなるのでしょうか?

export function MyCodeComponent() {
  const weather = use(weatherPromise)
  return <div>It’s {weather.temperature}° outside!</div>
}

サーバーでは、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>が存在しないかのように扱われるということです。

function SuspenseThatIsIgnored({ children }) {
  return <Suspense fallback={<Suspend />}>{children}</Suspense>
}

function Suspend() {
  use(someInfinitePromise)
}

ドキュメントには親の<Suspense>の境界で有効化されるとされていますが、親の境界がない場合でもまるでバウンダリーがないかのように動作できるので、フラッシュなしで望んだ動作だけを維持できるのです。

もちろん、ドキュメント化されていない動作に依存するのはリスクがありますが、短期的にはかなり安定した解決策となるでしょうね。

Level 4: 外部コンポーネント

ここで一段階、複雑になっていきます。Framerではコードを直接書くだけでなく、他の人が書いたコードを再利用することも可能です。さらに、このコードは他の人が作ったノーコードUIの中にネストされることもあります。

Form 컴포넌트 안의 Input 코드 에러를 나타냅니다.

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

모든 Form이 전체적으로 함께 깨져야 함에 대해 설명합니다.

Framerの哲学上、Formはサイト作成者にとって一つの「不透明な単位」として見ることができます。自分のコンポーネントではないため内部を確認することはできません。したがって、境界が衝突した際にも内部の一部だけが壊れるのではなく、全てのFormが一緒に全体的に壊れるようにしなければなりません。 そうすることで、製作者が直感的に衝突状況を理解できるからです。

Level 5…N: 最後の難問

私たちが解決しなければならなかった最後の難問をご紹介します。

  • Framerはコードのコンポーネントだけでなくコードオーバーライドもサポートしているため、Code Boundariesもこのケースまで全て考慮する必要がありました。

  • 多くのコードエラーは基本的に解読が難しいのですが、特にコードが縮小されている場合にはさらに困難です。このため、どのコンポーネントがエラーを起こしているのかを簡単に特定できるツールを新たに作成する必要がありました。

Code Boundaries에 대해 소개합니다.

単にReactエラーバウンダリーを解決することから始めたが、サーバー・クライアント環境の違い、Suspenseの複雑な動作、外部コード再利用など多くの難問を解決した後、ようやくCode Boundariesをアップデートすることができました。


この記事はFramer公式ブログの「Rabbit hole of React error handling」を翻訳・加工したコンテンツです。

ブログを共有する

ブログを共有する

ブログを共有する

Become a
Framer Expert

Framer依頼を受けられるプロなら誰でも応募可能です。
手数料なしで、プロとクライアントを直接つなぎます。

Become a
Framer Expert

Framer依頼を受けられるプロなら誰でも応募可能です。
手数料なしで、プロとクライアントを直接つなぎます。

Become a
Framer Expert

Framer依頼を受けられるプロなら誰でも応募可能です。手数料なしで、プロとクライアントを直接つなぎます。