この記事の目次
一覧を開いても反応が見えず、しばらくすると「資料がありません」と表示された。それが読込み中なのか、本当に資料がないのか、取得に失敗したのかで、利用者が次に取る行動は変わります。App Routerでは、それぞれの表示を置くファイルと範囲を決められます。
ReactのJSX、async関数、Server ComponentとClient Componentの区別が分かる人向けです。架空の資料棚をNext.js 16.4.5、React / React DOM 19.2.0、Node.js 24系で作ります。App Routerを使い、Cache Componentsは無効にします。実際のAPI通信は加えず、ローカルの模擬データで表示経路を確かめます。
資料棚の外枠を、三つの表示の外へ置く
今回の/catalogでは、共通の見出しと戻るリンクを残し、その下だけを切り替えます。待機はloading.jsx、予期しない例外はerror.jsx、資料が存在しない結果はnot-found.jsxへ割り当てます。全部を失敗と呼ぶと、待つべき場面と戻るべき場面が区別できません。
| ファイル | 表示する条件 | 共通の外枠 |
|---|---|---|
| app/catalog/loading.jsx | pageの処理を待っている | 残る |
| app/catalog/page.jsx | 正常に資料が見つかった | 残る |
| app/catalog/error.jsx | 内側で未処理の例外が起きた | 残る |
| app/catalog/not-found.jsx | notFoundを呼んだ | 残る |
外枠は同じフォルダのlayout.jsxへ置きます。loadingの公式資料では、pageや子の領域をSuspenseという待機境界で囲み、同じ階層のlayout自身は囲まない構造を確認できます。この配置を知ると、読み込みを待っても残る範囲を決められます。
状態をファイルで切り替える資料棚を作る
空フォルダで次を実行します。練習用のstate.jsonだけを編集して条件を変えます。
npm init -y
npm install --save-exact next@16.4.5 react@19.2.0 react-dom@19.2.0
mkdir -p app/catalog data
next.config.mjsとdata/state.jsonを、それぞれ保存します。
export default { cacheComponents: false };
{ "state": "ok" }
app/layout.jsxは文書全体の外枠です。読みやすさのための最小限の余白だけを付けます。
export const metadata = { title: '資料棚の状態確認' };
export default function Layout({ children }) {
return <html lang="ja"><body style={{ margin: 16, fontFamily: 'sans-serif', lineHeight: 1.8 }}>
<main style={{ maxWidth: 560, margin: 'auto' }}>{children}</main>
</body></html>;
}
app/page.jsxに入口を作ります。prefetchを無効にし、この練習ではクリック後の待機を観察しやすくしています。
import Link from 'next/link';
export default function Page() {
return <><h1>状態の確認</h1><p>架空の資料棚を開く練習です。</p>
<Link href="/catalog" prefetch={false}>資料棚を開く</Link></>;
}
app/catalog/layout.jsxに、読み込み中も残したい案内を書きます。
import Link from 'next/link';
export default function CatalogLayout({ children }) {
return <><h1>資料棚</h1><nav aria-label="練習の移動">
<Link href="/">状態の確認へ戻る</Link>
</nav>{children}</>;
}
app/catalog/page.jsxが読取りを担当します。connectionの後に1.2秒待ち、ファイルを読みます。connectionの資料に沿って要求時に実行し、ビルド時の一回の読取りで固定しない構成です。
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { connection } from 'next/server';
import { notFound } from 'next/navigation';
export default async function CatalogPage() {
await connection();
await new Promise(resolve => setTimeout(resolve, 1200));
const raw = await readFile(path.join(process.cwd(), 'data/state.json'), 'utf8');
const { state } = JSON.parse(raw);
if (state === 'missing') notFound();
if (state !== 'ok') throw new Error('Practice catalog failure');
return <section><h2>公開中の資料</h2><p>水辺の観察地図</p></section>;
}
順にapp/catalog/loading.jsx、app/catalog/error.jsx、app/catalog/not-found.jsxへ保存します。errorだけはClient
Componentです。
export default function Loading() {
return <p role="status">資料を読み込んでいます。</p>;
}
'use client';
export default function CatalogError({ retry }) {
return <section><h2>資料を開けませんでした</h2>
<p>時間をおいて再試行するか、上のリンクから戻ってください。</p>
<button type="button" onClick={() => retry()}>もう一度読み込む</button>
</section>;
}
export default function NotFound() {
return <section><h2>該当する資料はありません</h2>
<p>上のリンクから状態の確認へ戻れます。</p></section>;
}
公開用の動作を調べるため、npx next buildが成功した後、npx next start --hostname 127.0.0.1 --port 3007で起動します。http://127.0.0.1:3007から資料棚へ進むと、待機の文が資料名へ置き換わります。見え方の時間は通信や描画にも左右され、画面に正確に1.2秒出る保証ではありません。
待機と例外が届かないときは、囲まれる範囲を見る
第一の失敗は、pageの待機や読取りを同階層のcatalog/layout.jsxへ移すことです。catalog/loading.jsxはそのlayoutを囲まないため、そこに置いた待機を受け持てません。修正は、資料の読取りをpageへ戻すことです。入口から入り直し、外枠が読める間に待機の文が出るか再確認します。
errorの境界にも同様の範囲があります。同階層のlayoutで発生した例外は、そのerror.jsxでは扱えません。errorの公式資料に従い、外側の境界が必要な処理か、内側へ置く処理かを判断します。今回の見本は資料読取りを内側へ限定しています。
第二の失敗は、page全体をtry/catchで囲み、catchでは必ず通常のErrorを投げることです。notFoundも描画を中断する仕組みを使うため、広いcatchで捕まえると「該当なし」を取得失敗へ変えてしまいます。修正ではnotFoundの呼出しをそのcatchの外へ置きます。完成例は一括のcatchを設けていません。missingを入れて、再試行ボタンのない案内へ届くか確かめます。
再試行は、原因が変わった後にも試す
state.jsonを{ "state": "error" }へ変え、/catalogを再読み込みします。資料を開けない案内とボタンが出るはずです。そのまま再試行しても原因が残っているので、再びエラーになります。ボタンがあることと、復旧できることは別です。
次にファイルをokへ戻し、ページを手動で再読み込みせず「もう一度読み込む」を押します。16.4.5のretryは再取得と再描画を試みるので、資料名へ復帰するのが期待結果です。resetはエラー状態を解除して再描画する用途で、再取得を含む今回の操作にはretryを使います。古いバージョンへこのprops名をそのまま移しません。
error.messageをそのまま利用者へ表示する構成にもしていません。公開環境のServer Componentのエラー詳細は伏せられます。画面には次の操作を示し、原因はサーバー側の記録で調べます。クリックイベント内の任意の例外や入力値の不正まで、error.jsxがすべて処理するわけではありません。
三つの出口と、戻るリンクを照合する
15〜20分の確認では、次の表を上から試します。state.jsonは起動中でも読み直される教材ですが、実サービスの保存やキャッシュの更新手順を示す例ではありません。その課題はNext.jsのキャッシュの記事で別に扱います。
| 条件と操作 | 期待する最終表示 |
|---|---|
| okで入口から移動 | 待機の後に「水辺の観察地図」 |
| errorで/catalogを再読み込み | 失敗の文と再試行ボタン |
| 表示を残したままokへ戻し、再試行 | 資料名へ復帰 |
| missingで/catalogを再読み込み | 該当なしの文、再試行ボタンなし |
各状態で「資料棚」と戻るリンクが残り、TabとEnterで移動・再試行できるかも確認します。最後はokへ戻します。公開・運用の無料レッスンの確認記録にも、表示と操作を一組で残せます。
not-foundの資料が説明するとおり、ストリーミング開始後の該当なしはHTTP 200のまま表示される場合があります。この教材でも見出しとnoindexの指定を含めて確認し、「該当なし画面が出たからHTTP 404」と決め付けないでください。状態ごとの文と、利用者が戻れる経路がそろったところで確認を終えます。
TRY A LESSON
記事の内容を、課題で確かめる
記載内容へのご質問や訂正のご連絡は、hello@junkbranding.comまでお願いします。