こんにちは、かつコーチです。
前回は動的ルートとルートグループを使って、URLに応じたページ作りとフォルダ整理の方法を解説しました。
ページの構造ができても、データ取得中の「白い画面」やエラー発生時の「真っ白なエラーページ」をそのまま放置すると、ユーザー体験が悪くなってしまいます。
今回は、App Routerに用意されている特殊ファイル loading.tsx・error.tsx・not-found.tsx を使って、それぞれの状態を見た目よく整える方法を解説します。
loading.tsx・error.tsx・not-found.tsxとは?
3つの特殊ファイルの役割
App Routerでは、page.tsx と同じフォルダに特定のファイル名を置くだけで、特定の状態のUIを自動的に切り替えられます。
loading.tsx:データ取得中に表示されるローディングUIerror.tsx:レンダリング中に例外が発生したときのエラー画面not-found.tsx:該当データが見つからないときの404画面
いずれも自分でルーティングを書く必要はなく、ファイルを置くだけで自動的に組み込まれます。
なぜそれが必要なのか
これらのファイルがないと、データ取得中は何も表示されずユーザーが不安になったり、エラー時にNext.jsのデフォルトのエラー画面(開発中はスタックトレース)がそのまま表示されたりします。
内部的には、loading.tsx はSuspenseの境界、error.tsx はError Boundaryの境界として機能しており、Reactの仕組みをNext.jsが自動で配線してくれています。
基本の書き方
手順1:loading.tsxでローディングUIを出す
// app/products/loading.tsx
export default function Loading() {
return <p className="text-gray-500">商品情報を読み込み中...</p>;
}
同じ階層の page.tsx が非同期でデータを取得している間、Next.jsは自動的にこの Loading を表示してくれます。
手順2:error.tsxでエラー画面を出す
// app/products/[id]/error.tsx
"use client";
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<p>商品情報の取得に失敗しました。</p>
<p className="text-sm text-gray-400">{error.message}</p>
<button onClick={() => reset()}>再読み込み</button>
</div>
);
}
error.tsx は必ず "use client" を付ける必要があります。
reset 関数を呼ぶと、エラーになった部分だけを再レンダリングし、ページ全体をリロードせずに復旧を試みられます。
手順3:not-found.tsxで404画面を出す
// app/products/[id]/not-found.tsx
export default function NotFound() {
return <p>指定された商品が見つかりませんでした。</p>;
}
not-found.tsx は、page.tsx の中で notFound() 関数を呼び出したときに表示されます。
// app/products/[id]/page.tsx
import { notFound } from "next/navigation";
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const product = await getProduct(id);
if (!product) {
notFound(); // ここでnot-found.tsxが表示される
}
return <h1>{product.name}</h1>;
}
つまずきやすい設定・注意点
error.tsx はそのフォルダの layout.tsx 自体で発生したエラーは捕まえられません。
レイアウト自体のエラーまで扱いたい場合は、app/global-error.tsx という特別なファイルを用意する必要があります。
よくあるつまずきポイント・エラー対処
一次情報:’use client’を忘れてビルドエラーになった話
私が初めて error.tsx を作ったとき、他のサーバーコンポーネントと同じ感覚で "use client" を書き忘れてしまいました。
❌ Before:’use client’なしでerror.tsxを書く
// app/products/[id]/error.tsx
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<p>{error.message}</p>
<button onClick={() => reset()}>再読み込み</button>
</div>
);
}
見た目は普通のコンポーネントなので気づきにくいのですが、ビルド時に「error.tsx must be a Client Component」という趣旨のエラーが出て止まりました。
これは、error.tsx がReactのError Boundaryという仕組みの上に成り立っており、Error Boundaryはクライアント側の機能だからです。
✅ After:ファイル先頭に’use client’を追加
// app/products/[id]/error.tsx
"use client";
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<p>{error.message}</p>
<button onClick={() => reset()}>再読み込み</button>
</div>
);
}
error.tsx はNext.jsのルールとして必ずClient Componentになる、と覚えてしまうのが一番早い解決策です。
まとめ
この記事のポイント
loading.tsxはデータ取得中のローディングUIを自動表示するerror.tsxはエラー発生時のフォールバックUIで、必ず"use client"が必要not-found.tsxはnotFound()関数を呼んだときに表示される404画面- レイアウト自体のエラーには
global-error.tsxが必要になる
次に読むべき記事
UI面の整え方が分かったところで、次はApp Routerの核心である「サーバーとクライアントの役割分担」に踏み込んでいきます。
次回は、Server Componentsとは何か、これまでのReactとの一番の違いを解説します。
→ 次の記事:Server Componentsとは?Reactとの一番の違い