Slug segments, catch-all routes, async params in Next.js 15, generateStaticParams, and the full suite of client-side navigation hooks including the new useLinkStatus.
F-5 — Dynamic Routes, Params, and Navigation Hooks
Who this is for: Developers who understand Server Components and basic data fetching from F-3 and F-4, and now need to work with URL parameters, statically pre-render dynamic content, and navigate programmatically from Client Components. This module covers the full routing toolkit — including the navigation hooks that Next.js 15 and 16 extended significantly.
Dynamic Segments: The Mechanics
A folder with square brackets creates a dynamic segment. Every page inside that folder receives the matched value as a params prop:
The folder name — without brackets — becomes the key in params:
In Next.js 15+, params is a Promise. Always await it. If you're reading legacy code from Next.js 14, you'll see synchronous destructuring — that still works during the Next.js 15 transition period, but new code should await params.
Nested Dynamic Segments
You can nest dynamic segments. Each folder adds a key to params:
Deeply nested dynamic routes are common in content-heavy applications. Keep in mind that each level of nesting is a separate layout boundary — you can have app/users/[userId]/layout.tsx that fetches the user profile once and shares it across all pages under that user's namespace.
Catch-All and Optional Catch-All Segments
Catch-all [...slug] matches one or more path segments and returns them as an array:
Note: catch-all does not match the root path /docs — only paths with at least one additional segment.
Optional catch-all [[...slug]] also matches the root:
Optional catch-all is the pattern for CMS-driven sites where the homepage is content-managed alongside all other pages. The root check is if (!slug).
generateStaticParams — Pre-rendering Dynamic Routes
By default, dynamic routes render on-demand at request time (server-rendered). generateStaticParams tells Next.js to pre-render specific parameter values at build time, generating static HTML files for those paths.
At build time, Next.js calls generateStaticParams(), gets the list of IDs, and pre-renders each one. The resulting HTML files are stored and served directly from the CDN for those paths — no server work per request.
What happens to paths not in generateStaticParams? By default, they render on-demand (dynamic rendering). You can change this behaviour:
dynamicParams = false is the strict mode. Use it when your entire dataset is known at build time (a fixed set of blog posts, documentation pages) and you want 404s for any URL that isn't in your dataset.
For catch-all routes:
Each array entry maps to one path. ['api', 'authentication'] pre-renders /docs/api/authentication.
searchParams — The Query String
Pages also receive searchParams for the URL query string. Like params, it's a Promise in Next.js 15+:
Important: accessing searchParams makes a page dynamic — it can't be statically generated because the query string is only known at request time. If you need to pre-render a page with filtering, consider baking filter options into the URL path instead (/products/shoes rather than /products?category=shoes).
Navigation Hooks in Client Components
Server Components can't navigate programmatically — navigation is a browser action. These hooks live in Client Components:
useRouter
Programmatic navigation:
router.refresh() is the method you'll call most often from Client Components when server data changes and you need the Server Components to re-run. It doesn't do a full page reload — it re-fetches the current route's server-rendered content and merges it with the existing client state.
usePathname
The current URL path as a string:
useSearchParams
Reads the current URL query string. Returns a URLSearchParams-like object:
useSearchParams() requires a <Suspense> boundary when used in a component that isn't itself inside one. This is because it makes the component depend on search parameters that are only available at render time, and React needs a boundary to handle the async nature of this. You'll see this as a build warning if you forget it.
The typical pattern: wrap the component that uses useSearchParams in <Suspense> in the parent Server Component:
useParams
Reads dynamic route params from a Client Component (equivalent to params in Server Components, but as a synchronous hook):
useLinkStatus (Next.js 16)
A newer hook that tracks the prefetch and navigation state of links. Useful for showing loading indicators when navigating to slow routes:
useLinkStatus must be rendered inside a <Link> component to receive the status for that specific link. It returns { pending: boolean } — pending is true while the route is being prefetched or navigated to.
The <Link> Component and Prefetching
next/link is the standard way to navigate between pages. It renders an <a> tag but intercepts clicks to do client-side navigation rather than full page reloads.
Prefetching is the killer feature: as soon as a <Link> enters the viewport, Next.js starts prefetching the destination route in the background. By the time the user clicks, the data is already loaded — navigation feels instant.
The prefetch prop controls this:
In development mode, prefetching is disabled — it only runs in production builds. Don't be confused by the lack of prefetching when testing locally.
Linking with Dynamic Segments
For dynamic paths, construct the URL string directly:
The object form exists for type safety — it's used with Next.js's experimental typedRoutes feature, which validates that the route and its params match a real route in your codebase. Without typedRoutes, the string form is cleaner.
permanentRedirect() and Redirect Hierarchy
You've seen redirect() from F-4. For permanent (308) redirects:
The difference matters for SEO: redirect() sends a 307 temporary redirect, permanentRedirect() sends a 308 permanent redirect. Search engines treat permanent redirects as "consolidate link equity here" — use permanent redirects when you're retiring a URL for good, not when you're conditionally sending users to different destinations.
URL State as the Source of Truth
One pattern worth establishing early: for filters, pagination, search, and sort state — use the URL, not useState.
Why:
- URLs are shareable — a user can copy/paste a filtered view and it works
- Browser back button works correctly — pressing back restores the previous filter state
- Server Components receive the state directly via
searchParams— no client/server sync required - Bookmarking and deep linking work out of the box
The pattern:
The Server Component owns the data fetch. The Client Component owns the UI interaction. Neither knows about the other's implementation. The URL mediates between them. This is the most maintainable architecture for filter/sort/search patterns in Next.js.
Where We Go From Here
F-6 covers the built-in Next.js components — <Image>, fonts, <Script>, and the new <Form> component. These are the utilities that handle the performance concerns most engineers solve poorly with raw HTML: image optimization, font loading without layout shift, third-party script loading without blocking render.
After F-6 and F-7 (Route Handlers), F-8 brings everything together in a complete application build. By then you'll have the full vocabulary — server components, client components, data fetching, dynamic routes, and the built-in components — to build real applications.
Why does useSearchParams() require a <Suspense> boundary when used in a component that isn't itself inside one?
How does the prefetch behavior of the <Link> component operate by default in production?
When should you use permanentRedirect() instead of redirect()?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.
Sign in & RegisterDiscussion
0Join the discussion