Catch-all ба optional маршрут
[slug] параметр нэг сегментийг барьдаг. Гэвч /docs/react/hooks/useState гэх мэт хэд хэдэн сегменттэй замыг нэг файлаар барихад catch-all route хэрэгтэй болдог. Энэ хичээлд тэр тусгай синтаксыг судална.
[...slug] — Catch-all маршрут
Хаалтанд ... нэмбэл тухайн сегментээс хойших бүх замыг барьдаг:
app/docs/[...slug]/page.tsx
/docs/intro → slug = ["intro"]
/docs/react/hooks → slug = ["react", "hooks"]
/docs/react/hooks/use-state → slug = ["react", "hooks", "use-state"]
Параметрийн утга нь массив байна:
// app/docs/[...slug]/page.tsx
interface Props {
params: Promise<{ slug: string[] }>;
}
export default async function DocsPage({ params }: Props) {
const { slug } = await params;
// slug = ["react", "hooks", "use-state"]
const section = slug[0]; // "react"
const category = slug[1]; // "hooks"
const page = slug[2]; // "use-state"
return (
<main>
<p className="text-slate-400 text-sm">{slug.join(" / ")}</p>
<h1 className="text-white text-2xl font-bold">{slug[slug.length - 1]}</h1>
</main>
);
}
Анхаар: /docs хаягаар ороход энэ файл ажиллахгүй — slug хоосон байж болохгүй. Зөвхөн /docs/зүйл хаягаас эхэлнэ.
[[...slug]] — Optional catch-all маршрут
Давхар хаалт ашиглахад slug хоосон байж болно — тэр нь parent хуудсыг (энэ тохиолдолд /docs) мөн барьдаг:
app/docs/[[...slug]]/page.tsx
/docs → slug = undefined
/docs/intro → slug = ["intro"]
/docs/react/hooks → slug = ["react", "hooks"]
// app/docs/[[...slug]]/page.tsx
interface Props {
params: Promise<{ slug?: string[] }>;
}
export default async function DocsPage({ params }: Props) {
const { slug } = await params;
// slug байхгүй тохиолдолд нүүр хуудсыг харуул
if (!slug || slug.length === 0) {
return (
<main>
<h1 className="text-white text-2xl font-bold">Баримт бичиг</h1>
<p className="text-slate-400">Зүүн талаас сэдвийг сонгоно уу.</p>
</main>
);
}
return (
<main>
<h1 className="text-white text-2xl font-bold">{slug[slug.length - 1]}</h1>
<p className="text-slate-400 text-sm">Зам: {slug.join(" › ")}</p>
</main>
);
}
[...slug] ба [[...slug]] харьцуулалт
| | [...slug] | [[...slug]] |
| ------------- | ----------------------- | ------------------------ |
| /docs | ❌ Тохирохгүй | ✅ slug = undefined |
| /docs/intro | ✅ slug = ["intro"] | ✅ slug = ["intro"] |
| /docs/a/b/c | ✅ slug = ["a","b","c"] | ✅ slug = ["a","b","c"] |
| Хэзээ ашиглах | Заавал нэг+ сегмент | Parent хуудсыг ч барихад |
Бодит жишээ — агуулгын сайт
Documentation, blog, wiki зэрэг агуулгад catch-all маршрут маш тохиромжтой:
// app/content/[...path]/page.tsx
import { readFile } from "fs/promises";
import { notFound } from "next/navigation";
import path from "path";
import ReactMarkdown from "react-markdown";
interface Props {
params: Promise<{ path: string[] }>;
}
export default async function ContentPage({ params }: Props) {
const { path: segments } = await params;
// ["react", "hooks"] → "content/react/hooks.md"
const filePath = path.join(
process.cwd(),
"content",
...segments.slice(0, -1),
`${segments[segments.length - 1]}.md`,
);
let content: string;
try {
content = await readFile(filePath, "utf-8");
} catch {
notFound();
}
return (
<article className="prose prose-invert max-w-3xl mx-auto p-6">
<ReactMarkdown>{content}</ReactMarkdown>
</article>
);
}
generateStaticParams catch-all маршрутад
Catch-all маршруттай хуудсуудыг урьдчилан build хийхэд generateStaticParams нь массив буцаана:
export async function generateStaticParams() {
// Бүх боломжит замуудыг буцаана
return [
{ slug: ["intro"] },
{ slug: ["react", "hooks"] },
{ slug: ["react", "hooks", "use-state"] },
{ slug: ["typescript", "types"] },
];
}
Дараагийн хичээлд:
Route Group судална. Хаягт нөлөөлөхгүйгээр файлуудаа логикоор бүлэглэх, нэг route дотор хэд хэдэн өөр layout ашиглах аргыг ойлгоно.