Mastering Dynamic Routes in Next.js with [id].js
(2025 Edition)
Dynamic routes let you map a single React page to many URLs that share the same “shape”—think /blog/first-post
, /blog/second-post
, or /users/alice
. In Next .js, you create these flexible paths just by putting a segment in square brackets ([ ]
) inside the file-system router. This guide walks through everything you need to know in 2025, covering both the classic Pages Router and the newer App Router introduced in v13+, with tips for pre-rendering, data fetching, SEO, and common gotchas.
1. Quick refresher: file-system routing
Next.js turns folders & files into URL segments automatically.pages/about.js
→ /about
| pages/blog/first-post.js
→ /blog/first-post
When you don’t know the segment name until runtime, you wrap it in brackets.
2. Dynamic routes in the Pages Router
What you do | What it creates |
---|---|
pages/posts/[id].js |
Matches any /posts/{id} URL |
// pages/posts/[id].js
import { useRouter } from 'next/router';
export default function Post() {
const { query } = useRouter(); // { id: '123' }
return <h1>Post {query.id}</h1>;
}
Pre-rendering with getStaticPaths
+ getStaticProps
-
If the list of paths is finite (e.g. blog posts), export
getStaticPaths
to tell Next-.js which IDs to pre-render at build time and usegetStaticProps
to fetch data for each. Next.js -
fallback
lets you choose what happens for paths not in that list (false
,'blocking'
, ortrue
). -
Because these are generated to plain HTML/JSON, they’re CDN-friendly and great for SEO.
Catch-all segments
-
[...slug].js
captures/a/b/c
asslug: ['a','b','c']
. -
[[...slug]].js
does the same and matches the parent path (/
). Next.js
3. Dynamic routes in the App Router (Next.js 13-15)
The App Router uses folders instead of files for segments:
Pre-rendering with generateStaticParams
generateStaticParams
replaces getStaticPaths
in the App Router. Return an array of param objects, and Next.js will statically build one page per item: Next.js
-
For paths you don’t pre-generate, control runtime behavior with the
dynamicParams
route-segment option. -
Catch-all (
[...slug]
) and optional catch-all ([[...slug]]
) work the same here too. Next.js
4. Linking between dynamic pages
Whether you’re in pages/
or app/
, use <Link>
and provide the full href:
Next.js automatically prefetches the code bundle for that route on hover/viewport, giving near-instant navigation.
5. API routes with dynamic parameters
Dynamic file names work in pages/api/
(Pages Router) and app/api/
(App Router route handlers):
In the Pages Router you’d create pages/api/posts/[postId].js
and export default functions for the HTTP verbs.
6. SEO & performance checklist
-
Prefer static generation (
getStaticPaths
/generateStaticParams
) whenever the data can be cached publicly. -
When data is user-specific or changes often, use Server-Side Rendering (
getServerSideProps
) or dynamic = 'force-dynamic'` in App Router. -
Don’t forget to set canonical URLs and meta tags—dynamic routes are still SSR pages, so you can compute SEO metadata on the server.
7. Common pitfalls & debugging tips
Issue | Fix |
---|---|
Page shows as 404 during next build |
Make sure the [id].js file exports getStaticPaths when using SSG. |
“Router query is empty on first render” | Remember that on the very first client render during static generation, query params might be undefined—guard against it. |
Dynamic param undefined in App Router | Confirm that the folder is named [param] , not (param) (parentheses create a route group). |
ISR paths not updating | Return empty array or set revalidate /dynamicParams so Next.js can rebuild paths at runtime. |
8. Conclusion
With just a pair of square brackets, you unlock powerful, SEO-friendly routing that scales from ten pages to ten million. Use [id].js
(Pages Router) or [id]/page.js
(App Router) for the basic pattern, sprinkle in getStaticPaths
or generateStaticParams
to pre-render, and you’re production-ready. Happy routing! 🚀
FAQs: Dynamic Routes in Next .js ([id].js
& App Router folders)
# | Question | Short Answer |
---|---|---|
1 | What’s the difference between dynamic routes in the Pages Router and the App Router? | In Pages, you create a file like pages/posts/[id].js ; in App, you create a folder named [id] with page.js inside. Data-fetching helpers change from getStaticPaths/Props (Pages) to generateStaticParams & segment options (App). |
2 | Do I always need getStaticPaths() / generateStaticParams() ? |
Only if you want static generation (SSG/ISR). If you prefer on-demand rendering (SSR) or the list of possible paths is huge/unknown, skip them and set the route to be dynamic at runtime (getServerSideProps in Pages; dynamic = 'force-dynamic' in App). |
3 | How do optional segments work—what’s [[...slug]] for? |
[[...slug]].js (Pages) or [[...slug]]/page.js (App) matches zero or more segments, so both /blog and /blog/a/b hit the same page. Without the double brackets, at least one segment is required. |
4 | Can I have multiple dynamic segments? | Yes. Example: pages/users/[userId]/posts/[postId].js → /users/42/posts/7 . In App Router: app/users/[userId]/posts/[postId]/page.js . |
5 | How do I link to a dynamic page? | Use <Link href={ /posts/${id}}>…</Link> (both routers). You can pass objects ({ pathname:'/posts/[id]', query:{ id } } ) if the route has search params too. |
6 | Why do I see an empty router.query on first render? |
In statically generated Pages routes, the initial HTML is rendered without JS, so router.query is undefined until hydration. Guard for it (if (!id) return null ). |
7 | What does fallback: 'blocking' do? |
During SSG (build), paths not returned by getStaticPaths render at request time on the server once, then get cached. Users wait (“blocking”) for the HTML. |
8 | How do I revalidate stale static pages? | Pages Router: return { revalidate: 60 } from getStaticProps . App Router: export revalidate = 60 or use fetch(url, { next:{ revalidate:60 }}) . |
9 | Can dynamic API routes use the same pattern? | Absolutely. Example (Pages): pages/api/posts/[id].js . App Router: app/api/posts/[id]/route.ts . |
10 | Will catch-all routes hurt SEO? | Not if you pre-render or set suitable robots /canonical tags. Each real URL should still output unique meta tags and canonical hrefs. |
11 | How do I generate a sitemap for dynamic pages? | Use getStaticPaths / generateStaticParams inside a script or API route to list all slugs, then emit XML. For very large sets, stream the sitemap or split it per 50 k URLs. |
12 | Any common mistakes to avoid? | ✓ Forgetting to export getStaticPaths → build-time 404. ✓ Using (group) folders instead of [segment] in App. ✓ Hard-coding <a href> instead of <Link> . ✓ Not handling undefined params on first client render. |