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.

Notes

FolderMatchesparams
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

Next.js
// 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:

Angular
// app.config.tsprovideRouter(routes, withComponentInputBinding());​// app.routes.ts{ path: 'posts/:slug', loadComponent: () => import('./posts/post.component').then((m) => m.PostComponent) }
Angular
// 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 params is a Promise; forgetting await is a common upgrade error from Next.js 14.
  • Angular's rxResource re-fetches automatically when slug() changes; in a Next.js Server Component, a new slug is a new server render.
  • Catch-all [...parts] ≈ Angular's ** wildcard with a custom UrlMatcher.

Quick reference

Next.jsAngular
[slug] folder:slug path
await paramsinput.required<string>() + withComponentInputBinding()
await searchParamsQuery params as inputs, or ActivatedRoute.queryParamMap
useParams() (client)ActivatedRoute.paramMap