第35章:Catch-all:[...slug] の使いどころ🪤✨
今日は 「URLの深さがバラバラでも、1つのページで受け止める」 ための技、Catch-all ルートをやるよ〜!🧡
1) Catch-all ってなに?🤔💡
たとえば、こういうURLたち👇
/docs/intro/docs/intro/getting-started/docs/intro/getting-started/install
何階層になるか分からないときに、Next.jsはこれを…
app/docs/[...slug]/page.tsx
みたいに書くと、ぜんぶキャッチしてくれるよ〜🪤✨
このとき slug は 配列になるのがポイント!
/docs/a/b/c → slug = ["a","b","c"] みたいな感じだよ🎯 (nextjs.org)
2) どんなときに使う?🧭📚
よくある使いどころはこんな感じ👇
- ドキュメントサイト(章・節・項みたいに深くなる)📘
- カテゴリが何段にもなるEC(/shop/clothes/tops みたいな)🛍️
- CMS的なページ(/pages/2025/12/24 みたいな)📰
3) 図でイメージしよ〜🖼️✨(Mermaid)
4) 実装してみよう!🛠️✨(最小で動くやつ)
ここでは「簡易ドキュメント」を作って、Catch-allを体験するよ📚💕
(1) フォルダ構成📁
app/
docs/
page.tsx
[...slug]/
page.tsx
/docsは一覧ページ(普通のルート)/docs/〜〜/〜〜は Catch-all が担当🪤
(2) /docs の一覧ページを作る(app/docs/page.tsx)📄🔗
import Link from "next/link";
export default function DocsIndexPage() {
return (
<main style={{ padding: 24 }}>
<h1>Docs 一覧📚✨</h1>
<ul>
<li>
<Link href="/docs/intro">はじめに🌱</Link>
</li>
<li>
<Link href="/docs/intro/getting-started">導入ガイド🚀</Link>
</li>
<li>
<Link href="/docs/guide/routing/catch-all">Catch-all 解説🪤</Link>
</li>
</ul>
<p style={{ marginTop: 16 }}>好きな階層のURLに飛んでみてね😆💖</p>
</main>
);
}
(3) Catch-all 本体(app/docs/[...slug]/page.tsx)🪤✨
※ 最近のNext.jsでは params が Promise なので、await して取り出すよ〜! (nextjs.org)
import Link from "next/link";
import { notFound } from "next/navigation";
const PAGES: Record<string, { title: string; body: string }> = {
"intro": {
title: "はじめに🌱",
body: "ここは /docs/intro だよ〜!Next.jsの道のりへようこそ😊✨",
},
"intro/getting-started": {
title: "導入ガイド🚀",
body: "ここは /docs/intro/getting-started だよ〜!まずは動かして慣れちゃお🧡",
},
"guide/routing/catch-all": {
title: "Catch-all 解説🪤",
body: "Catch-allは、URLの深さが増えても1つのページで受け止められる便利技だよ✨",
},
};
export default async function DocsCatchAllPage({
params,
}: {
params: Promise<{ slug: string[] }>;
}) {
const { slug } = await params; // ✅ slugは配列!
const path = slug.join("/");
const page = PAGES[path];
if (!page) notFound(); // 🚪 存在しないページは404へ
return (
<main style={{ padding: 24 }}>
<p>
<Link href="/docs">← Docs一覧へ戻る📚</Link>
</p>
<h1 style={{ marginTop: 12 }}>{page.title}</h1>
<p style={{ marginTop: 12 }}>{page.body}</p>
<hr style={{ margin: "24px 0" }} />
<h2>いま受け取った slug(配列)🧩</h2>
<ul>
{slug.map((seg, i) => (
<li key={i}>
{i}: {seg}
</li>
))}
</ul>
<p style={{ marginTop: 12 }}>
今のパス: <b>/docs/{path}</b> ✨
</p>
</main>
);
}
これで👇が全部このページに来るよ🪄
/docs/intro/docs/intro/getting-started/docs/guide/routing/catch-all(そしてslugが配列で入ってくる!) (nextjs.org)
5) ついでに:Optional Catch-all も知っておく?👀✨
Catch-allの「親玉」みたいなやつで、
app/docs/[[...slug]]/page.tsx
って書くと /docs も同じページで受け止められるよ!
この場合、/docs に来たときは slug が undefined になり得るから注意⚠️ (nextjs.org)
6) よくあるハマり🐣🧯
slugを 文字列だと思ってslug.toUpperCase()とかすると壊れる💥 → 配列だよ!slug.join("/")みたいに使うのが基本✨ (nextjs.org)- ルート直下(
app/[...slug])に置くと、他のルートを飲み込みやすい🥺 → まずは今回みたいにdocs/の下に置くのが安心だよ〜🫶 (GitHub)
7) ミニ練習🎯💖
PAGESに好きなキーを追加してみてね(例:"guide/ui/buttons")✨/docs/guide/ui/buttonsにアクセスして表示されれば成功🎉- 存在しないURLを叩いて、ちゃんと404になるのも確認しよ〜🚪
まとめ🍀✨
app/docs/[...slug]/page.tsxで 深いURLをまとめて受け取れる🪤slugはstring[](配列)!/a/b→["a","b"](nextjs.org)- 最近は
paramsが Promise なので、await paramsで取り出すのが安心🫶 (nextjs.org)
次に進む前に、ぜひいろんな深さのURLを自分で作って遊んでみてね😆💖