Next.js の App Router では、error.tsx を置くことでそのセグメント配下のエラーを捕まえてエラー画面を出せます。ところが、ルートレイアウト(app/layout.tsx)そのものでエラーが起きた場合、app/error.tsx はレイアウトの内側に描画されるため出番がありません。この「レイアウト自体が壊れたとき」の最後の受け皿が global-error.tsx です。この記事では、global-error.tsx の書き方、error.tsx との違い、html と body を自分で書かなければならない理由、そして開発中は見えないという挙動までを解説します。
目次
global-error.tsx とは(アプリ全体の最後の受け皿)
app/global-error.tsx は、App Router のルート階層に置く特別なファイルです。ルートレイアウトやルートテンプレートで発生したエラーを含む、アプリケーション全体のエラーを捕捉します。error.tsx がそのセグメントのレイアウトの内側に描画されるのに対し、global-error.tsx はルートレイアウトを置き換えて描画されるのが最大の違いです。
ルートレイアウトを置き換えるということは、そこで定義していた <html> や <body> も一緒に失われるということです。そのため global-error.tsx では、これらのタグを自分で書く必要があります。共通のヘッダーやフッター、グローバル CSS の読み込みもルートレイアウトごと失われるので、シンプルな画面にしておくのが安全です。
基本の書き方
error.tsx と同じく、global-error.tsx は必ずクライアントコンポーネント('use client')にします。エラーの内容を受け取る error と、再描画を試みる reset の2つの props を受け取ります。
'use client';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
// ルートレイアウトを置き換えるので html・body を自分で書く
<html lang="ja">
<body>
<h2>問題が発生しました</h2>
<p>お手数ですが、しばらくしてから再度お試しください。</p>
<button onClick={() => reset()}>もう一度試す</button>
</body>
</html>
);
}
html と body を書き忘れると、エラー画面が正しく描画されません。「エラーが起きたのに真っ白な画面になる」という症状の多くはこれが原因です。
props の中身(error と reset)
受け取る2つの props の役割は次のとおりです。digest は本番環境で重要になるプロパティで、エラーメッセージの代わりにサーバーログと突き合わせるための識別子です。
| props | 説明 |
|---|---|
error | 発生した Error オブジェクト。本番ではメッセージが「Application error: a server-side exception has occurred」のような汎用文言に置き換えられる |
error.digest | サーバーで生成されたエラーのハッシュ。サーバーログの該当エラーと突き合わせるために使う |
reset | エラー境界の再描画を試みる関数。成功すれば元の画面に戻る |
本番でエラーの詳細が伏せられるのは、機密情報の漏洩を防ぐための仕様です。原因を追いたいときは digest をユーザーに表示しておき、問い合わせを受けたらその値でサーバーログを検索する、という運用にすると調査がしやすくなります。
'use client';
import { useEffect } from 'react';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// 監視サービスへの通知など。ここは必ずクライアント側で動く
console.error(error);
}, [error]);
return (
<html lang="ja">
<body>
<main>
<h2>問題が発生しました</h2>
{error.digest && (
<p>エラーID: <code>{error.digest}</code></p>
)}
<button onClick={() => reset()}>もう一度試す</button>
<a href="/">トップページへ戻る</a>
</main>
</body>
</html>
);
}
トップページへのリンクに next/link ではなく <a> を使っている点にも意味があります。ルートレイアウトが壊れている状況ではクライアント側のルーティングが正常に動かないことがあるため、ページ全体を読み込み直す通常のリンクの方が確実です。
error.tsx との違いと使い分け
2つのファイルは似ていますが、捕捉する範囲と描画のされ方が異なります。日常的なエラー表示は error.tsx が担当し、global-error.tsx はそこで拾えなかった場合の保険という位置づけです。
| 観点 | error.tsx | global-error.tsx |
|---|---|---|
| 置く場所 | 任意のセグメント | app/ の直下のみ |
| 捕捉する範囲 | 同じセグメントの page と配下の子 | ルートレイアウトを含むアプリ全体 |
| 描画される位置 | そのセグメントのレイアウトの内側 | ルートレイアウトを置き換える |
html / body | 不要(レイアウトが持っている) | 自分で書く必要がある |
| 共通ヘッダー等 | そのまま残る | 失われる |
両方を置いた場合、まず近い方の error.tsx が処理を試み、それでも捕まえられなかったとき(=ルートレイアウトやルートテンプレート自体のエラー)に global-error.tsx が使われます。多くのアプリでは app/error.tsx と app/global-error.tsx を両方用意しておくのが定石です。
捕捉できないエラーもある
global-error.tsx は万能ではありません。React のエラー境界は、あくまでレンダリング中に発生した例外を捕まえる仕組みです。次のようなものは対象外なので、別の方法で対処します。
まず、イベントハンドラーの中で投げられたエラーは捕捉されません。onClick の処理が失敗する可能性があるなら、その中で try...catch して状態に持たせ、画面に反映する必要があります。同じく setTimeout や Promise のコールバックなど、レンダリング後に非同期で走る処理のエラーもエラー境界の外です。
また、notFound() や redirect() が内部的に投げる特殊なエラーは Next.js が専用に処理するため、エラー画面にはなりません。not-found.tsx が表示されたり、リダイレクトが実行されたりする正常な動作です。ただし、try...catch の中でこれらを呼ぶと catch 節に吸い込まれて動かなくなるので、try ブロックの外で呼ぶようにしてください。
開発中に global-error.tsx が表示されないとき
開発モードではエラーオーバーレイが優先される
next dev で動かしていると、エラーが起きても自作のエラー画面ではなく Next.js の開発用オーバーレイ(赤いエラー表示)が出ます。これは仕様で、開発中はスタックトレースを見せる方が有用だからです。global-error.tsx の見た目を確認したいときは、next build と next start で本番モードを起動して試してください。
‘use client’ を書き忘れている
エラー境界は内部的にクラスコンポーネントの componentDidCatch を使う仕組みで、クライアント側でしか動きません。'use client' がないとビルド時にエラーになります。ファイルの先頭行に必ず書いてください。
html・body を書いていない
前述のとおり、global-error.tsx はルートレイアウトを置き換えます。<div> だけを返していると html 要素が存在しない状態になり、画面が真っ白になります。error.tsx からコピーして作るときにやりがちなミスです。
グローバル CSS が効かない
ルートレイアウトで import './globals.css' していた場合、そのレイアウトごと置き換わるため、エラー画面ではスタイルが当たらないことがあります。エラー画面用の最低限のスタイルは、インラインスタイルで直接書くか、global-error.tsx の中で CSS を読み込む形にしておくと確実です。
まとめ
app/global-error.tsx は、ルートレイアウトを含むアプリケーション全体のエラーを受け止める最後の砦です。error.tsx がレイアウトの内側に描画されるのに対し、global-error.tsx はルートレイアウトを置き換えるため、<html> と <body> を自分で書く必要があります。ファイル先頭の 'use client' は必須で、error と reset の2つの props を受け取ります。本番ではエラーメッセージが伏せられるので、error.digest を画面に出しておくとサーバーログとの突き合わせに役立ちます。イベントハンドラー内や非同期処理のエラーは捕捉されないこと、notFound() や redirect() は対象外であることも押さえておきましょう。開発モードではエラーオーバーレイが優先されるため、表示を確認するときは next build と next start で本番モードを起動してください。