Next.js: The React Framework (App Router)Entry 4 of 16
Dynamic Routes and Params
A folder named in square brackets ([slug]) matches any value in that URL segment and passes it to the page as params.
nextjs.orgDynamic Segments
Notes
| Folder | Matches | params |
|---|---|---|
app/posts/[slug]/page.tsx | /posts/hello | { slug: 'hello' } |
app/shop/[...parts]/page.tsx | /shop/a/b | { parts: ['a', 'b'] } |
app/docs/[[...parts]]/page.tsx | /docs and /docs/a/b | { parts: undefined } or ['a', 'b'] |
Since Next.js 15, params and searchParams are Promises. await them in Server Components, or use useParams() / useSearchParams() in Client Components.
What / Why / When
- What
One page file that serves many URLs.
- Why
Detail pages, slugs, and IDs without a file per item.
- When
Any URL with a variable part.
- Use case
A blog post page at
/posts/[slug].
Code compare
// app/posts/[slug]/page.tsximport { getPost } from "@/lib/posts";type Props = { params: Promise<{ slug: string }>; searchParams: Promise<{ tab?: string }>;};export default async function PostPage({ params, searchParams }: Props) { const { slug } = await params; const { tab = "content" } = await searchParams; const post = await getPost(slug); return ( <article> <h1>{post.title}</h1> {tab === "comments" ? <p>Comments…</p> : <div>{post.body}</div>} </article> );}With withComponentInputBinding(), route params and query params arrive as component inputs:
// app.config.tsprovideRouter(routes, withComponentInputBinding());// app.routes.ts{ path: 'posts/:slug', loadComponent: () => import('./posts/post.component').then((m) => m.PostComponent) }// post.component.tsimport { Component, computed, inject, input } from "@angular/core";import { rxResource } from "@angular/core/rxjs-interop";import { PostService } from "./post.service";@Component({ selector: "app-post", template: ` @if (post.value(); as post) { <article> <h1>{{ post.title }}</h1> @if (tab() === "comments") { <p>Comments…</p> } @else { <div>{{ post.body }}</div> } </article> } `,})export class PostComponent { slug = input.required<string>(); // from :slug tab = input<string>("content"); // from ?tab= private posts = inject(PostService); post = rxResource({ params: () => ({ slug: this.slug() }), stream: ({ params }) => this.posts.getPost(params.slug), });}Differences and gotchas
- Next.js
paramsis a Promise; forgettingawaitis a common upgrade error from Next.js 14. - Angular's
rxResourcere-fetches automatically whenslug()changes; in a Next.js Server Component, a new slug is a new server render. - Catch-all
[...parts]≈ Angular's**wildcard with a customUrlMatcher.
Quick reference
| Next.js | Angular |
|---|---|
[slug] folder | :slug path |
await params | input.required<string>() + withComponentInputBinding() |
await searchParams | Query params as inputs, or ActivatedRoute.queryParamMap |
useParams() (client) | ActivatedRoute.paramMap |