Routing, links and navigation

The Link component and prefetching, the navigation hooks, route groups and catch-all segments, and when to redirect.

import Link from 'next/link'

export default function Nav() {
  return (
    <nav>
      <Link href="/guides">Guides</Link>
      <Link href="/guides/intro" scroll={false}>Intro (keep scroll position)</Link>
      <Link href={{ pathname: '/guides/[slug]', params: { slug: 'intro' } }}>Typed href</Link>
      <Link href="/dashboard" replace>Replace the history entry</Link>
      <Link href="/api/export" prefetch={false}>Download</Link>
    </nav>
  )
}
  • Link renders an anchor and performs a client-side navigation: the new route's tree replaces the old one without reloading the document.
  • In production, links in the viewport are prefetched, so the target's code is usually already downloaded when the user clicks. Prefetching is off in development.
  • A dynamic route needs the concrete path (/guides/intro), not the folder pattern with brackets.
  • scroll={false} is the right choice for filters and tabs; replace is for a navigation the back button should skip.
  • Use a plain anchor for external URLs, downloads and mail links. A plain anchor to an internal route also works, but it costs a full document request.

useRouter, usePathname and useSearchParams

'use client'

import { useRouter, usePathname, useSearchParams } from 'next/navigation'

export default function Filters() {
  const router = useRouter()
  const pathname = usePathname()            // '/guides', no query string
  const params = useSearchParams()          // read-only URLSearchParams
  const tag = params.get('tag') ?? 'all'

  function setTag(next) {
    const usp = new URLSearchParams(params)
    next === 'all' ? usp.delete('tag') : usp.set('tag', next)
    router.push(pathname + '?' + usp.toString(), { scroll: false })
  }

  return <button onClick={() => setTag('vue')}>Vue ({tag})</button>
}
  • In the App Router these hooks come from next/navigation. next/router still exists for the Pages Router, and mixing the two imports is a frequent mistake.
  • useSearchParams is read-only. To change the query you build a new URL and push or replace it.
  • router.refresh() re-renders the server components for the current route — the way to pick up fresh data after a mutation without a full reload.
  • Only client components can use the hooks. In a server component you read the same values from the params and searchParams props instead.
  • usePathname excludes the query string. Compare a trimmed path for active links, and be careful with startsWith so /guides-new does not light up /guides.
💡
Everything in next/navigation is client-only. A server component already receives the same values as props — params and searchParams — so read them there instead of shipping a client boundary just to know which page is rendering.

Route groups, catch-all segments and redirects

app/
  (marketing)/
    about/page.tsx              -> /about      (group name is not in the URL)
    pricing/page.tsx            -> /pricing
  (shop)/
    layout.tsx                  -> shell shared by every route in the group
  docs/[...slug]/page.tsx       -> /docs/a/b   catch-all
  shop/[[...filters]]/page.tsx  -> /shop and /shop/a   optional catch-all
  old/page.tsx                  -> redirect('/new')
  • Parentheses group routes without adding a segment, which is how one site gets a marketing shell and an app shell with different layouts.
  • A catch-all segment matches one or more levels and arrives as an array; the optional form also matches the bare parent path.
  • redirect() from next/navigation works by throwing a control-flow signal, so never call it inside a try block that swallows exceptions.
  • Use notFound() for a missing resource and a config redirect or middleware for URL moves that must apply before anything renders.
  • Nesting is about layout and data scope, not about the URL: a folder with a layout and no page is a legitimate structure.

FAQ

Why is nothing prefetched in development?
Prefetching is disabled in development so a stale cache does not hide your changes. Measure navigation behaviour against a production build, where visible links are prefetched and navigation is usually instant.
Link or a plain anchor?
Use Link for in-app routes so navigation stays client-side and prefetching applies. Use an anchor for external destinations, file downloads and anything that must leave the app.

Setting up a project with create-next-app Middleware, headers, cookies and sessions

Last refreshed 2026-09-18.