3.

Next.js の layout / loading / error の役割

編集

この記事の要点

  • layout.tsx は階層ごとに入れ子になり、画面遷移しても再描画されず状態を保つ。ヘッダーやサイドバーの置き場所。
  • loading.tsx を置くと、そのセグメントが自動的に Suspense 境界で包まれ、読み込み中の表示が出る。
  • error.tsx はエラー境界。必ずクライアントコンポーネントにする必要がある。
  • これらは描画順が決まっている。外側から layout → template → error → loading → not-found → page の順に入れ子になる。
  • 遷移のたびに作り直したい枠は layout ではなく template を使う。

App Router には、ファイル名そのものが役割を持つ「特殊ファイル」がいくつかあります。名前を置くだけで React の Suspense 境界やエラー境界が組み込まれるため、仕組みを知らずに使うと「なぜかローディングが出ない」「エラー画面が効かない」といった状態になりがちです。この記事では各ファイルの役割と、正しい置き場所を整理します。

1特殊ファイルの一覧

ファイル役割置く場所の目安
layout配下ページ共通の外枠。遷移しても状態を保つルート必須。区画ごとに任意
pageその URL の本体公開したいすべてのパス
loading読み込み中の表示(Suspense 境界)データ取得のあるページ
error例外を受け止める境界失敗が想定される区画
global-errorルートレイアウト自体の失敗を受け止めるapp/ 直下のみ
not-found404 の表示ルート、または区画ごと
template遷移のたびに作り直される外枠入場アニメなどが必要な区画
routeAPI エンドポイントpage と同じ階層には置けない
default並行ルートのフォールバック@slot を使う場合は必須

2レイアウトは入れ子になる

layout.tsxchildren を受け取り、その中にページや下位のレイアウトが差し込まれます。フォルダ階層に沿って自動的に入れ子になるため、共通部分を書く場所を階層で選べます。

// app/blog/layout.tsx
export default function BlogLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <section>
      <nav>ブログ内ナビ</nav>
      {children}
    </section>
  )
}

重要なのは、レイアウトは画面遷移しても再描画されないという点です。/blog/a から /blog/b へ移動しても app/blog/layout.tsx はそのまま残り、内部の state やスクロール位置が保たれます。開いたままのアコーディオンや入力途中のフォームがリセットされないのはこのためです。

app/layout.tsx(ルートレイアウト・必須) app/blog/layout.tsx error.tsx(例外をここで受け止める) loading.tsx(Suspense 境界) page.tsx
同じセグメントに置いた特殊ファイルは、この順で外側から内側へ入れ子になる。error が loading より外側にあるため、読み込み中に起きた例外もエラー境界で受け止められる。

ルート直下の app/layout.tsx だけはルートレイアウトと呼ばれ、必須かつ htmlbody を含む必要があります。ルートグループを使えば複数のルートレイアウトを持つこともできますが、その場合はそれぞれに htmlbody が要ります。

3loading.tsx で読み込み中を見せる

loading.tsx を置くだけで、そのセグメントの page 以下が自動的に Suspense 境界で包まれます。データ取得の完了を待つあいだ、レイアウトは表示したままローディング表示に切り替わります。

// app/blog/loading.tsx
export default function Loading() {
  return <p>読み込み中…</p>
}

スピナーよりも、実際の画面に近いスケルトン(見出しやカードの枠だけを描いたもの)のほうが体感は良くなります。ここで一つ落とし穴があります。同じセグメントの layout がデータを取りに行っている場合、その待ち時間は同じ階層の loading では隠せません。レイアウト自身の描画が終わるまで遷移がブロックされるためです。回避するには、レイアウト内のデータ取得部分を個別に Suspense で包むか、取得処理を page 側へ移します。詳しい挙動は ストリーミングで体感速度を上げる で扱います。

4error.tsx はクライアントコンポーネント

error.tsx は、その配下で起きた描画時の例外を受け止めて代替 UI を出します。React のエラー境界そのものなので、ファイル先頭に 'use client' が必須です。これを忘れるとエラー境界として機能しません。

'use client'   // エラー境界はクライアントコンポーネントであること

import { useEffect } from 'react'

export default function ErrorPage({
  error,
  retry,
}: {
  error: Error & { digest?: string }
  retry: () => void
}) {
  useEffect(() => {
    console.error(error)   // 監視サービスへ送るならここ
  }, [error])

  return (
    <div>
      <h2>問題が発生しました</h2>
      <button onClick={() => retry()}>再試行</button>
    </div>
  )
}

受け取る props は error と、その区画をやり直す retry です。エラーは最も近い親のエラー境界まで伝播するので、区画ごとに error.tsx を置けば、画面全体を巻き込まずに一部だけを差し替えられます。

ルートレイアウト自体が壊れた場合はこの仕組みでは受け止められません。そのときに使うのが app/global-error.tsx で、ルートレイアウトを置き換える形で表示されるため、自分で htmlbody を書く必要があります

5not-found.tsx で 404 を作る

該当データが無いときは notFound() を呼びます。呼ばれると最も近い not-found.tsx が表示されます。

import { notFound } from 'next/navigation'

export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  const post = await getPostBySlug(slug)

  if (!post) {
    notFound()
  }

  return <div>{post.title}</div>
}

「見つからない」は障害ではなく正常な結果なので、throw new Error() で表現しないでください。エラー境界に落ちると 500 系の扱いになり、検索エンジンにも意図しない信号を送ることになります。

6layout と template の違い

layout を使う
ヘッダー・サイドバー・タブなど、遷移しても保ったままにしたい枠。state もスクロール位置も維持される。ほとんどの場合はこちら。
template を使う
遷移ごとにマウントし直したい枠。入場アニメーションを毎回走らせたい、useEffect を遷移のたびに再実行したい、といった限定的な用途。

template.tsxlayout.tsx と同じく children を受け取りますが、遷移のたびに新しいインスタンスが作られます。両方置いた場合は layout の内側に template が入ります。

7つまずきやすいところ

  • error.tsx が効かない'use client' が抜けているのが最頻出の原因です。
  • イベントハンドラ内のエラーが捕まらない:エラー境界は描画中の例外だけを扱います。クリック処理などは try / catch で捕まえ、useState に保持して表示を切り替えます。
  • ローディングが一瞬も出ない:データ取得が layout 側にあるか、そもそも取得が速すぎるかのどちらかです。
  • 全ページに loading.tsx を置いてしまう:一覧だけに出したい場合は、ルートグループを作ってその中に置くと、URL を変えずに適用範囲を絞れます。

ここまでで画面の骨格が組めました。次はいよいよ App Router の中核である、サーバーとクライアントの境界に進みます。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 開発環境の構築とプロジェクトの初期構成
  2. App Router のルーティングとファイル規約
  3. レイアウトと特殊ファイルの使い分け
  4. サーバーコンポーネントとクライアントコンポーネント
  5. サーバー側でのデータ取得と並列化
  6. ストリーミングで体感速度を上げる
  7. キャッシュの仕組みと use cache
  8. 再検証の使い分け
  9. フォーム送信とデータ更新の実装
  10. エラーハンドリングとエラー境界
  11. Route Handlers で API を作る
  12. メタデータ API で SEO を整える
  13. 画像とフォントの最適化
  14. 本番ビルドとセルフホスト
  15. バージョン16 への移行と注意点