Dziennik / Rozwój produktów webowych
Jak skonfigurować dynamiczne trasy w Next.js. Pełne omówienie [[...slug]]
![Jak skonfigurować dynamiczne trasy w Next.js. Pełne omówienie [[...slug]]](/_next/image?url=%2Fimages%2Fblogs%2Fnext-routing-slug.webp&w=3840&q=75)
Dynamiczne routing w Routerze aplikacji zapewniają elastyczność przy budowie bloga, katalogu, sklepu lub witryny dokumentacyjnej. Podstawowe przypadki użycia można obsługiwać za pomocą [id] [...slug], ale w przypadku sekcji z opcjonalnym zagnieżdżaniem najbardziej wygodną architekturę zapewnia [[...slug]].
W tym artykule omówimy wszystkie trzy wzorce, ale skupimy się głównie na [[...slug]]: jak pasuje do adresów URL, co przychodzi w params, jak bezpiecznie zadeklarować go, jak generować statyczne ścieżki, jak budować breadcrumby i jak obsługiwać SEO.
Szybka Mapa
[slug]- jeden segment Przykład:/blog/my-postparams.slug: string[[...slug]]- wymagany catch-all (potrzebny jest przynajmniej jeden segment) Przykład:/docs/getting-started/installparams.slug: string[][[...slug]]- opcjonalny catch-all (pasuje zarówno do roota, jak i dowolnej liczby segmentów) Przykłady:/shop,/shop/men,/shop/men/t-shirtsparams.slug: string[] | undefined
Kiedy użyć [[...slug]]
- Jeden komponent i układ dla zarówno roota, jak i wszystkich zagnieżdżonych poziomów sekcji. Przykład: dokumentacja lub katalog, gdzie strona główna sekcji i podstrony mają taką samą strukturę.
- Potrzebujesz strony root bez segmentów, plus nielimitowanego zagnieżdżania poniżej. Przykład:
/docsjako spis treści i głębsze ścieżki, takie jak/docs/guides/setup/cloud. - Breadcrumby i nawigacja są budowane z tablicy segmentów, ale strona root również musi działać.
Podstawowa Implementacja [[...slug]]
Struktura katalogu:
app/
docs/
[[...slug]]/
page.tsx
layout.tsx
page.tsx:
import { notFound } from 'next/navigation';
type PageProps = {
params: { slug?: string[] };
};
async function getNodeByPath(slug: string[]) {
const path = '/' + slug.join('/');
const res = await fetch(`${process.env.API_URL}/docs?path=${encodeURIComponent(path)}`, {
cache: 'force-cache'
});
if (!res.ok) return null;
return res.json() as Promise<{ title: string; html: string } | null>;
}
export default async function DocsPage({ params }: PageProps) {
const segments = params.slug ?? []; // At the root, it's undefined, so convert to []
const node = await getNodeByPath(segments);
if (!node) notFound();
return (
<article>
<h1>{node.title}</h1>
<div dangerouslySetInnerHTML={{ __html: node.html }} />
</article>
);
}
Główne punkty:
- Na root,
params.slugbędzieundefined. Natychmiast przekonwertuj go na[]. - Trasa pasuje zarówno do
/docsjak i do dowolnych zagnieżdżeń.
Breadcrumby od [[...slug]]
import Link from 'next/link';
type CrumbsProps = { segments: string[] };
export function Breadcrumbs({ segments }: CrumbsProps) {
const items = [
{ label: 'Docs', href: '/docs' },
...segments.map((s, i) => ({
label: decodeURIComponent(s),
href: '/docs/' + segments.slice(0, i + 1).map(encodeURIComponent).join('/')
}))
];
return (
<nav aria-label="breadcrumb">
<ol>
{items.map(item => (
<li key={item.href}>
<Link href={item.href}>{item.label}</Link>
</li>
))}
</ol>
</nav>
);
}
Użycie:
export default function DocsPage({ params }: { params: { slug?: string[] } }) {
const segments = params.slug ?? [];
return (
<>
<Breadcrumbs segments={segments} />
{/* content */}
</>
);
}
Najlepsze praktyki:
- Zawsze używaj
encodeURIComponentpodczas generowania linków. - Do wyświetlania możesz odkodować, aby pokazać ludzie-czytelne nazwy.
Generowanie Statycznych Ścieżek dla [[...slug]]
Jeśli znasz jakieś ścieżki z góry i chcesz je zbudować z wyprzedzeniem, użyj generateStaticParams. Z opcjonalnym catch-all, możesz generować zarówno zagnieżdżone ścieżki, jak i root.
export const revalidate = 300;
export async function generateStaticParams() {
return [
{ slug: [] }, // corresponds to /docs
{ slug: ['getting-started'] }, // corresponds to /docs/getting-started
{ slug: ['guides', 'install'] } // corresponds to /docs/guides/install
];
}
Uwagi:
- Dla
[[...slug]]wygodne jest opisanie rootowej ścieżki jako{ slug: [] }. To jasno wskazuje, że trzeba wygenerować ścieżkę bez segmentów. - Aby ściśle ograniczyć prawidłowe statyczne ścieżki, użyj:
SEO i Linki Kanoniczne dla [[...slug]]
generateMetadata otrzymuje params i pozwala generować metadane na podstawie segmentów.
import type { Metadata } from 'next';
type Props = { params: { slug?: string[] } };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const segments = params.slug ?? [];
const path = '/docs' + (segments.length ? '/' + segments.join('/') : '');
const res = await fetch(`${process.env.API_URL}/docs/meta?path=${encodeURIComponent(path)}`, {
cache: 'force-cache'
});
if (!res.ok) return { title: 'Documentation' };
const meta = await res.json() as { title: string; description?: string; canonical?: string };
return {
title: meta.title,
description: meta.description,
alternates: { canonical: meta.canonical ?? path },
openGraph: { title: meta.title, description: meta.description }
};
}
Rekomendacje:
- Bądź ostrożny z duplikatami adresów URL. Jeśli używasz
trailingSlashupewnij się, że kanoniczne pasuje do faktycznej konfiguracji. - Dla stron opartych na danych, ustaw rozsądny
revalidateaby metadane były zawsze świeże.
Kontrola Cache i Renderowania
- Statyczne domyślnie z ISR:export const revalidate = 300;
- W pełni dynamiczna odpowiedź:
await fetch(url, { cache: 'no-store' });
// lub na poziomie routingu: export const dynamic = 'force-dynamic';
- Podejście mieszane: cacheuj niektóre dane, pobieraj inne dane na bieżąco. Dla często aktualizowanych bloków rozważ Uchwyty Tras z własnym cache.
[[...slug]] vs [...slug] and Common Pitfalls
- Root handling
[[...slug]]matches both the root and nested paths. At the root,params.slugisundefined. Normalize it to[].[...slug]never matches the root. At least one segment is required. - Typing Always type explicitly:
params: { slug?: string[] }. This prevents errors when accessingparams.slug.length. - Static paths Add
{ slug: [] }if you want to prebuild the root. With dynamicParams = false, any path not returned by generateStaticParams will give a 404.LinksNever concatenate segments directly. UseencodeURIComponentwhen generating hrefs. - Route conflicts Route groups (e.g.
(marketing)) don’t appear in the URL but affect layout hierarchy. Keep - [[...slug]] isolated in its section if you have similar patterns.
404sAlways callnotFound()for invalid paths. Customize app/not-found.tsx for your UX. - **Complete Example of a Documentation Section with **[[...slug]]
layout.tsx:page.tsx:
generateStaticParams.ts :
app/
docs/
layout.tsx
[[...slug]]/
page.tsx
loading.tsx
not-found.tsx
not-found.tsx:
export default function DocsLayout({ children }: { children: React.ReactNode }) {
return (
<div className="docs">
<aside>{/* section menu */}</aside>
<main>{children}</main>
</div>
);
}
Pre-Release Checklist/docs
import { notFound } from 'next/navigation';
import { Breadcrumbs } from './_components/Breadcrumbs';
type PageProps = { params: { slug?: string[] } };
async function getDoc(path: string) {
const res = await fetch(`${process.env.API_URL}/docs?path=${encodeURIComponent(path)}`, {
next: { revalidate: 300 }
});
if (!res.ok) return null;
return res.json() as Promise<{ title: string; html: string } | null>;
}
export default async function DocsPage({ params }: PageProps) {
const segments = params.slug ?? [];
const path = '/docs' + (segments.length ? '/' + segments.join('/') : '');
const doc = await getDoc(path);
if (!doc) notFound();
return (
<article>
<Breadcrumbs segments={segments} />
<h1>{doc.title}</h1>
<div dangerouslySetInnerHTML={{ __html: doc.html }} />
</article>
);
}
opens the root page. Deep paths like
/docs/a/b/c render correctly.
export default function NotFound() {
return <div>Page not found</div>;
}
params.slug
is normalized to[]- at the root.
Breadcrumbs generate correct, encoded links.generateMetadata returns proper title and canonical for both root and nested pages.notFound()works for invalid nodes.revalidate- and
dynamicsettings match freshness requirements.Key Takeaways[[...slug]]provides a clean architecture for sections where both the root and nested pages share a single page and layout. It simplifies navigation, breadcrumbs, SEO, and overall maintenance.The most important points:Typeparams.slug
properly and normalize it to
[] at the root.
Build links safely with
- encodeURIComponent
.Prebuild paths with{ slug: [] }if needed. - Handle SEO with
generateMetadata. - Always return
notFound()for invalid paths. - Dynamic routes in Next.js: full breakdown of [[...slug]]
Detailed guide on dynamic routes in Next.js: [slug], [...slug] and [[...slug]]. Explaining how parameters work, generating static paths, SEO, and handling 404.. - Always return
notFound()for invalid paths.