Next.js / Catch-all ба optional маршрут

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"]

Параметрийн утга нь массив байна:

tsx
// 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"]
tsx
// 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 маршрут маш тохиромжтой:

tsx
// 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 нь массив буцаана:

tsx
export async function generateStaticParams() {
  // Бүх боломжит замуудыг буцаана
  return [
    { slug: ["intro"] },
    { slug: ["react", "hooks"] },
    { slug: ["react", "hooks", "use-state"] },
    { slug: ["typescript", "types"] },
  ];
}

Дараагийн хичээлд:

Route Group судална. Хаягт нөлөөлөхгүйгээр файлуудаа логикоор бүлэглэх, нэг route дотор хэд хэдэн өөр layout ашиглах аргыг ойлгоно.