React・本番で気をつけること

本番のエラーに気づく

手元では出ないエラーが本番では出ます。Error Boundaryから送る形、ソースマップで難読化されたスタックを読み戻す方法、何を送ってはいけないか、そしてどこまでやるかの線引きを扱います。

公開したら終わり、ではありません。手元では出ないエラーが、本番では出ます。

  • 古いブラウザだけで落ちる
  • 通信が遅い環境でだけタイミングがずれる
  • 特定のデータでだけundefinedを触る

そして利用者は報告してくれません。黙って離脱します。だから機械に見張らせます。

Error Boundaryは「見せる」係、監視は「知らせる」係

Error Boundaryの回で、画面が真っ白にならないようにしました。あれは利用者に見せるための仕組みです。

開発者が知るための仕組みは別に要ります。ちょうどcomponentDidCatchが、その受け渡し口になっています。

componentDidCatch(error, errorInfo) {
  // ここが「開発者に知らせる」場所
  sendToMonitoring(error, errorInfo.componentStack)
}

errorInfo.componentStackにはどの部品の中で起きたかが入っています。ファイル名と行番号だけより、はるかに追いやすい情報です。

自前で最低限を書くならこうなります。

window.addEventListener('error', (event) => {
  report({ message: event.message, source: event.filename, line: event.lineno })
})

window.addEventListener('unhandledrejection', (event) => {
  report({ message: String(event.reason), kind: 'promise' })
})

実務では、この2つに加えてまとめ送り・重複の除去・利用者の操作履歴が要るので、専用のサービスを入れるのが早いです(Sentry・Rollbar・Datadogなど。無料の枠で足りることがほとんどです)。

ソースマップが無いと読めない

本番のJavaScriptはまとめて短くされているので、そのままのエラーはこうなります。

TypeError: Cannot read properties of undefined (reading 'name')
  at t (index-BNfh9nLk.js:1:48213)

tが何なのか分かりません。これを元のコードに読み戻すのがソースマップです。

export default defineConfig({
  build: {
    sourcemap: true,      // .js.map を書き出す
  },
})

何を送ってはいけないか

監視ツールは送れるものを何でも送ってしまうので、ここは自分で止めます。

  • URLのクエリ?token=…?email=…
  • フォームの入力値(監視ツールの「操作の記録」機能に入りがち)
  • Authorizationヘッダー・Cookie
  • localStorageの中身

セキュリティの回で「フロントに秘密は置けない」と書きましたが、うっかり外に送ってしまう経路としてはここがいちばん現実的です。多くのサービスに送信前に加工する仕組みがあるので、そこで落とします。

// 送る直前に、危ない情報を消す
beforeSend(event) {
  delete event.request?.cookies
  return event
}

エラー以外に見るもの

同じ仕組みで、壊れてはいないが遅いも拾えます。

指標何を見ているか
LCPいちばん大きい要素が出るまでの時間(=体感の表示速度)
INP押してから反応するまでの時間(=もたつき)
CLS表示中に要素がずれる量(読んでいる途中で動く不快さ)

Reactのアプリで効くのは、たいてい大量データの回で扱った内容です——画像のサイズ指定でCLS入力の間引きでINPコード分割でLCP

配信をCloudflare PagesやVercelにしているなら、設定を入れるだけの計測機能が付いていることが多いので、まずそれで足ります。

どこまでやるか

状況やること
個人の小さいサイト監視サービスを無料枠で1つ入れる。それだけで十分
仕事のサービスエラー監視+パフォーマンス計測+通知の設計
社内向けツールエラー監視だけでよいことが多い
静的なページだけ入れなくてよい
React画面全体を落とさない(Error BoundaryとSuspense)Error Boundaryの回。「見せる」側の仕組みで、この回の「知らせる」と対になります

公式ドキュメントも見てみる

developer.mozilla.org外部サイトWindow: unhandledrejection イベント - Web API | MDNMDNのunhandledrejection。Error Boundaryが拾えない「catchし忘れたPromise」を捕まえる、標準の仕組みです

このレッスンのまとめ

  1. Error Boundaryは見せる係、監視は知らせる係。両方要る
  2. componentDidCatcherrorInfo.componentStackどの部品で起きたかを教えてくれる
  3. Error Boundaryはイベントハンドラ・setTimeout・catchし忘れたPromiseを拾えない
  4. だからerrorunhandledrejectionも見る
  5. ソースマップが無いと本番のエラーは読めない。ただし配信ファイルには含めない
  6. クエリ・入力値・トークン・Cookieは送らない。送信前に落とす
  7. 利用者は内部のIDで識別する(氏名やメールではなく)
  8. LCP・INP・CLSも同じ仕組みで拾える。対策は大量データの回と同じ
  9. 通知は絞る。鳴りっぱなしの警報は無いのと同じ

次からは最後の章です。まずはReact 19の新しい書き方から。

やってみよう:拾えないエラーを見つける

Error Boundaryの穴を、自分の目で確かめます。

  1. ボタンのクリック処理の中で、わざとundefined.nameを読む
  2. Error Boundaryが反応しない(画面はそのまま、コンソールにだけ出る)ことを確認する
  3. main.tsxwindow.addEventListener('error', …)unhandledrejectionを足す
  4. 1番をもう一度押して、今度は自分のログに出ることを確認する
  5. void Promise.reject(new Error('テスト'))を書いて、unhandledrejectionが出ることを確認する
window.addEventListener('unhandledrejection', (event) => {
  console.log('拾えていなかったPromiseの失敗:', event.reason)
})

2番が、この回でいちばん見ておいてほしいところです。Error Boundaryを入れたから安心、ではないと分かります。確認できたら、わざと壊したコードは消してください。

よくある質問

個人サイトでもエラー監視は要りますか?
アクセスがある限り価値はあります。利用者は不具合をわざわざ報告してくれないので、入れていなければ壊れていること自体に気づけません。無料の枠で足りることがほとんどです。
ソースマップを本番に置いても大丈夫ですか?
公開の場所に置くと元のコードが読まれます。監視サービスにアップロードだけして、配信ファイルには含めないのが基本です。
エラーは全部通知すべきですか?
全部にすると通知が読まれなくなります。新しく出た種類と、件数が急に増えたものだけ通知し、残りは一覧で見る形にします。
個人情報を送ってしまわないか心配です。
URLのクエリ、フォームの入力値、トークンは送らない設定にします。多くのサービスに送信前に加工する仕組みがあるので、そこで落とします。