Square Brackets Make a Segment Variable
Dynamic Routes — [slug]
A folder name wrapped in square brackets captures that segment as a route parameter the page can read.
What you'll learn
- Create `app/blog/[slug]/page.tsx`
- Read `params` inside the page component
- Use `generateStaticParams` for static generation
A dynamic route lets one folder match many URLs. Wrap the folder name in square brackets, and Next.js passes the captured value to your page.
A Dynamic Segment
app/blog/[slug]/page.tsx
# matches /blog/hello, /blog/intro-to-rust, /blog/anything The page receives a params object whose key is the bracket name.
// app/blog/[slug]/page.tsx
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return <h1>Post: {slug}</h1>;
} In Next.js 15, params is a Promise. Always await it before destructuring.
Multiple Dynamic Segments
You can nest dynamic folders.
app/[shop]/[item]/page.tsx
# /acme/widget → params = { shop: 'acme', item: 'widget' } // app/[shop]/[item]/page.tsx
export default async function ItemPage({
params,
}: {
params: Promise<{ shop: string; item: string }>;
}) {
const { shop, item } = await params;
return (
<p>
Shop {shop}, item {item}
</p>
);
} Static Generation with generateStaticParams
To pre-render dynamic routes at build time, export generateStaticParams. It returns
an array of params objects, and Next.js builds one page per entry.
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts: Array<{ slug: string }> = await fetch(
'https://api.example.com/posts',
).then((r) => r.json());
return posts.map((p) => ({ slug: p.slug }));
}
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await fetchPost(slug);
return <article>{post.title}</article>;
} Unknown slugs fall back to on-demand rendering by default. The next lesson covers catch-all segments for matching many path parts at once.
Catch-All Routes →