App Router の特殊ファイルを理解する — Next.js Tips
App Router では、フォルダ名が URL になるだけではありません。ファイル名そのものが役割 になります。page.tsx と layout.tsx はすでに触った前提で、残りを地図として置いておきます。
全部を最初から覚える必要はありません。名前を見て「あ、あれか」と戻ってこられれば十分です。
参考: Next.js — Project structure · Next.js — File conventions
目次
なぜ特殊ファイルがあるか
Pages Router だと _app.tsx や自前コンポーネントで共通処理を組み立てがちでした。App Router では、次のようなことを 決まったファイル名 で宣言します。
- 画面 →
page.tsx - 共通枠 →
layout.tsx - 読み込み中 →
loading.tsx - エラー →
error.tsx
覚える名前は増えますが、「どこに何を置くか」がプロジェクト間で揃いやすいです。
よく使うファイル
いまの段階で意識するのは、だいたいこれだけです。
| ファイル | 役割 | URL を作るか |
|---|---|---|
page.tsx |
その URL の画面 | する |
layout.tsx |
下のページを共通枠で囲む | しない |
loading.tsx |
読み込み中の UI | しない |
error.tsx |
その範囲のエラー UI(Client Component) | しない |
not-found.tsx |
404 UI。notFound() からも表示 |
しない |
route.ts |
HTTP API(Route Handler) | する(例: /api/hello) |
要点はひとつです。page.tsx(または route.ts)があるフォルダだけがルートになる。
ブラウザで /blog/demo を開いたときのイメージです(全部のファイルが毎回出るわけではありません)。
flowchart TD Req["リクエスト /blog/demo"] --> MW{"middleware.ts\nあれば先に実行"} MW --> RL["app/layout.tsx\n全体の共通枠"] RL --> BL["blog/layout.tsx\nブログ用の枠"] BL --> LD{"blog/loading.tsx\n読み込み中なら表示"} LD --> PG["blog/[slug]/page.tsx\n記事画面"] PG --> OK["レスポンス"] PG -.-> ER["途中で例外 → error.tsx"] PG -.-> NF["notFound() → not-found.tsx"]API(route.ts)のときは画面系の layout / page ではなく、対応する route.ts が応答します。
あとでよいファイル
一覧には載せておきますが、最初はスルーして大丈夫です。
| ファイル | ひとこと |
|---|---|
template.tsx |
layout に似るが、遷移のたびに再マウント |
default.tsx |
Parallel Routes(@slot)用。単一ルートではまだ出ない |
global-error.tsx |
root layout ごと落ちたとき用。<html> / <body> が必要 |
forbidden.tsx / unauthorized.tsx |
認可失敗 UI(設定が必要な場合あり) |
middleware.ts |
リクエスト前の横断処理(src/ またはプロジェクトルート) |
sitemap.ts / robots.ts / icon など |
SEO・アイコン用の特殊パス |
instrumentation.ts |
サーバー起動時フック |
画面と共通枠
page.tsx
その URL の本体です。src/app/about/page.tsx があれば /about が生えます。layout.tsx だけあっても URL はできません。
layout.tsx
そのフォルダより下にあるページを、共通の枠で囲みます。ヘッダーなどを 各ページにコピペするのではなく、枠の中にページがはまります。差し込み口が {children} です。
// イメージ<body> <header>...</header> {children} {/* ← ここに / や /about、/blog/... が入る */}</body>root の layout.tsx は <html> と <body> を持ち、全体共通に向きます。blog/layout.tsx を足すと、外側の枠 → 内側の枠 → ページの順になります。
template.tsx(あとでよい)
見た目は layout に近いですが、遷移のたびに中身が作り直されます。アニメーションを毎回最初から始めたいとき向けです。普通の共通枠は layout.tsx で足ります。
読み込み・エラー・404
loading.tsx
その範囲の読み込み中 UI です。about/loading.tsx を置けば /about 用のスケルトンになります。中では Suspense 境界として働きます。
最短だとこんな感じです。
// src/app/blog/loading.tsxexport default function Loading() { return <p>読み込み中…</p>;}error.tsx
例外が起きたときの画面です。Client Component である必要があります(reset で再試行するため)。親の layout までは巻き戻しません。not-found.tsx(見つからない)とは別物です。
not-found.tsx
URL が無いときや、コードから notFound() を呼んだときに出ます。普段の 404 カスタムはここからです。global-error.tsx は root layout 自体が落ちたレアケース用なので、先に使うのは通常の error.tsx です。
API と middleware
route.ts
画面ではなく、GET / POST などの HTTP メソッドを export します。
// src/app/api/hello/route.ts → /api/helloexport async function GET() { return Response.json({ message: "hello" });}page.tsx と同じフォルダには置けません。API は api/ など別フォルダに分けます。
middleware.ts(あとでよい)
マッチしたリクエストの手前で動きます。ログインチェックやリダイレクト向きです。置き場所は src/app/ の中ではなく、src/middleware.ts かプロジェクトルートです。
Metadata 系(sitemap.ts / icon など)は、画面とは別ラインの特殊パスだと思えば大丈夫です。
同居できない組み合わせ
いちばんハマりやすいルールです。
| 組み合わせ | 結果 |
|---|---|
同じフォルダに page.tsx と route.ts |
不可。UI と API はフォルダを分ける |
layout.tsx だけ |
URL はできない。page.tsx が別途必要 |
loading.tsx / error.tsx / not-found.tsx と page.tsx |
可。同じセグメントに並べてよい |
# OKsrc/app/about/page.tsxsrc/app/about/loading.tsx# NGsrc/app/about/page.tsxsrc/app/about/route.ts ← 同じフォルダはだめ# OK(API は別フォルダ)src/app/api/about/route.tsよくある勘違い
| 思いがち | 実際 |
|---|---|
layout.tsx を置けばページになる |
page.tsx が必要 |
error.tsx と not-found.tsx は同じ |
例外 UI と 404 UI で別 |
| 全部 root に置けばよい | セグメントごとに置ける。blog/loading.tsx は /blog 以下向け |
route.ts は pages/api と同じ場所感覚 |
App Router では app 配下のフォルダ+route.ts |
| 特殊ファイルを全部最初から用意する | 不要。必要になったときに足す |
いまのプロジェクトでの位置づけ
ファイルベースルーティングで触ったのは、だいたい次です。
src/app/├── layout.tsx ← 共通枠├── page.tsx ← /├── about/page.tsx ← /about└── blog/ ├── layout.tsx └── [slug]/page.tsxここに例えば次を足すと、役割がはっきりします。
src/app/blog/loading.tsx… 記事読み込み中src/app/not-found.tsx… 独自 404src/app/api/hello/route.ts… JSON API
特殊ファイルは「全部覚える」より、一覧を見て必要なときに開く 使い方で十分です。