Rotas Dinâmicas
Use a sintaxe de colchetes para criar rotas que correspondem a segmentos de URL dinâmicos - parâmetros únicos, segmentos catch-all e padrões opcionais de catch-all.
Busque em todas as páginas da documentação
Use a sintaxe de colchetes para criar rotas que correspondem a segmentos de URL dinâmicos - parâmetros únicos, segmentos catch-all e padrões opcionais de catch-all.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
app/
├── blog/[slug]/page.tsx # /blog/hello-world → { slug: "hello-world" }
├── docs/[...path]/page.tsx # /docs/a/b/c → { path: ["a", "b", "c"] }
└── shop/[[...categories]]/page.tsx # /shop or /shop/a/b → { categories: ["a", "b"] } or {}
// app/blog/[slug]/page.tsx
export default async function BlogPost({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return <h1>Post: {slug}</h1>;
}Quando usar isso: Qualquer rota onde a URL contém uma variável - IDs de produto, nomes de usuário, caminhos de documentação ou prefixos de localidade.
// app/blog/[slug]/page.tsx - Segmento dinâmico único
import { notFound } from "next/navigation";
interface Post {
slug: string;
title: string;
content: string;
}
async function getPost(slug: string): Promise<Post | null> {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 3600 },
});
if (!res.ok) return null;
return res.json();
}
export default async function BlogPostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
// Geração estática para slugs conhecidos
export async function generateStaticParams() {
const posts: Post[] = await fetch("https://api.example.com/posts").then((r) =>
r.json()
);
return posts.map((post) => ({ slug: post.slug }));
}// app/docs/[...path]/page.tsx - Rota catch-all
export default async function DocsPage({
params,
}: {
params: Promise<{ path: string[] }>;
}) {
const { path } = await params;
// /docs/getting-started/install → path = ["getting-started", "install"]
const fullPath = path.join("/");
return (
<div>
<h1>Docs: {fullPath}</h1>
<p>Segmentos: {path.length}</p>
</div>
);
}// app/shop/[[...categories]]/page.tsx - Catch-all opcional
export default async function ShopPage({
params,
}: {
params: Promise<{ categories?: string[] }>;
}) {
const { categories } = await params;
if (!categories || categories.length === 0) {
return <h1>Todos os Produtos</h1>;
}
return (
<div>
<h1>Shop: {categories.join(" > ")}</h1>
<p>Filtrando por {categories.length} categorias</p>
</div>
);
}[slug] corresponde a um único segmento. /blog/hello corresponde, /blog/hello/comments não.[...path] corresponde a um ou mais segmentos. /docs/a corresponde, /docs/a/b/c corresponde, mas /docs sozinho NÃO corresponde.[[...path]] corresponde a zero ou mais segmentos. Igual ao catch-all, mas também corresponde ao caminho base (/shop sem segmentos).Promise no Next.js 15+. Você deve await params em Server Components ou use(params) em Client Components.generateStaticParams pré-renderiza rotas dinâmicas no momento da build. Retorne um array de objetos de parâmetros, e o Next.js gerará uma página estática para cada um.app/[lang]/blog/[slug]/page.tsx produz params: { lang, slug }.not-found.tsx quando nenhuma página corresponde, ou quando você chama notFound().// Múltiplos segmentos dinâmicos
// app/[locale]/blog/[slug]/page.tsx
export default async function LocalizedPost({
params,
}: {
params: Promise<{ locale: string; slug: string }>;
}) {
const { locale, slug } = await params;
return <h1>{locale}: {slug}</h1>;
}
export async function generateStaticParams() {
return [
{ locale: "en", slug: "hello" },
{ locale: "fr", slug: "bonjour" },
];
}// generateStaticParams com parâmetros pai
// app/[category]/[product]/page.tsx
export async function generateStaticParams({
params,
}: {
params: { category: string };
}) {
const products = await getProductsByCategory(params.category);
return products.map((p) => ({ product: p.slug }));
}// Rota dinâmica com generateMetadata
// app/blog/[slug]/page.tsx
import type { Metadata } from "next";
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const post = await getPost(slug);
return {
title: post?.title ?? "Não encontrado",
description: post?.content?.slice(0, 160),
};
}// Tipos de parâmetros por padrão
type SingleParam = { slug: string }; // [slug]
type CatchAllParam = { path: string[] }; // [...path]
type OptionalCatchAll = { categories?: string[] }; // [[...categories]]
type MultiParam = { locale: string; slug: string }; // [locale]/[slug]
// Todos envolvidos em Promise para páginas Next.js 15+
type PageProps = {
params: Promise<SingleParam>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};
// Tipo de retorno de generateStaticParams
// Retorna objetos de parâmetros SEM o wrapper Promise
type StaticParams = SingleParam[];[...path] NÃO corresponde à rota base. /docs retornará 404 a menos que você tenha app/docs/page.tsx separadamente. Use [[...path]] se precisar da base./product/42 retorna { id: "42" }, não um número. Analise manualmente.generateStaticParams é executado no momento da build. Se sua fonte de dados não estiver disponível durante a build, a build falhará. Use dynamicParams = true (o padrão) para permitir a renderização sob demanda./blog/about corresponde a app/blog/about/page.tsx antes de app/blog/[slug]/page.tsx.{ params, slug } diretamente na assinatura da função não funciona mais no Next.js 15+. Sempre await.dynamicParams = false retorna 404 para parâmetros não listados. Apenas os caminhos retornados por generateStaticParams funcionarão.// Desativa a renderização dinâmica sob demanda
export const dynamicParams = false;| Abordagem | Quando Usar |
|---|---|
Rota estática (app/about/page.tsx) | A URL é fixa e conhecida com antecedência |
Grupos de Rotas (group) | Organize sem adicionar segmentos de URL |
| Middleware rewrites | Mapeie URLs personalizadas para rotas dinâmicas existentes |
searchParams em vez de parâmetros de caminho | Filtragem ou ordenação que não necessita de URLs únicas |
[slug] corresponde exatamente a um segmento de URL (ex: /blog/hello)[...path] corresponde a um ou mais segmentos (ex: /docs/a/b/c) mas não à rota base[[...path]] corresponde a zero ou mais segmentos, incluindo a rota base (ex: /shop ou /shop/a/b)O catch-all [...path] requer pelo menos um segmento. /docs sozinho não corresponde. Crie um app/docs/page.tsx separado para a rota base ou mude para o catch-all opcional [[...path]].
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return <h1>{slug}</h1>;
}Você deve await params porque eles agora são uma Promise.
dynamicParams = falseSim. Mesmo que a URL contenha um número como /product/42, o valor do parâmetro é a string "42". Você deve analisá-lo manualmente (ex: Number(id) ou parseInt(id)).
Rotas estáticas têm prioridade. /blog/about corresponde a app/blog/about/page.tsx antes de app/blog/[slug]/page.tsx.
Apenas os caminhos retornados por generateStaticParams funcionarão. Qualquer outro caminho dinâmico retorna um 404.
export const dynamicParams = false;import type { Metadata } from "next";
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const post = await getPost(slug);
return { title: post?.title ?? "Não encontrado" };
}type SingleParam = { slug: string };
type CatchAllParam = { path: string[] };
type OptionalCatchAll = { categories?: string[] };
type MultiParam = { locale: string; slug: string };
// Todos envolvidos em Promise para props de página
type PageProps = {
params: Promise<SingleParam>;
};Não no Next.js 15+. O padrão de desestruturação síncrona { params: { slug } } não funciona mais porque params agora é uma Promise. Sempre await params dentro do corpo da função.
// app/[category]/[product]/page.tsx
export async function generateStaticParams({
params,
}: {
params: { category: string };
}) {
const products = await getProductsByCategory(params.category);
return products.map((p) => ({ product: p.slug }));
}Os parâmetros do segmento pai são passados para o generateStaticParams do filho.
Chame notFound() de next/navigation para acionar o not-found.tsx mais próximo.
import { notFound } from "next/navigation";
const post = await getPost(slug);
if (!post) notFound();Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥