skip to content

In the Next.js App Router, what happens differently when a user clicks a `<Link href="/dashboard">` compared with a plain `<a href="/dashboard">`?

level: juniorimportance: must knowfreq 85%

answer

  1. one keeps the document, one replaces it
  2. what crosses the wire is not HTML
  3. only the changed segments are requested
  4. shared layout state survives, page state does not
  5. it still renders a real anchor element

basics

~20 s

Link performs a client-side navigation: Next fetches only the changed route's React Server Component payload and swaps it in, keeping the running app alive. A plain anchor triggers a full document load that discards everything.

solid answer

~40 s

`<Link>` from `next/link` renders a real `<a>` element, but it intercepts the click and hands the navigation to the App Router instead of the browser. The router requests the RSC payload for the segments that actually changed, merges it into the existing React tree, and updates the URL with the History API — the document is never reloaded, so the JavaScript runtime, shared layouts, and client component state stay alive. It also prefetches the destination as the link comes into view in production, so the swap usually feels instant. A plain `<a>` does a full document navigation: new HTML request, new JS parse and hydration, all client state gone, scroll reset. You still use a plain `<a>` deliberately for external URLs or when you *want* a hard reload.

code

tsx · 15 lines
tsx
import Link from 'next/link';

export default function SiteNav() {
  return (
    <nav>
      <Link href="/dashboard">Dashboard</Link>
      <Link href="/settings" replace>
        Settings
      </Link>
      <a href="https://status.example.com" target="_blank" rel="noopener noreferrer">
        Status page
      </a>
    </nav>
  );
}

go deeper

for a junior

Be able to say plainly that <Link> navigates without reloading the page while <a> reloads it, and that <Link> comes from next/link. Mention that external URLs still use a plain anchor.

for a middle

Explain the mechanics: Next intercepts the click, requests the RSC payload for the changed segments only, and updates the URL through the History API, so shared layouts are not re-rendered.

for a senior

Show judgment about what survives a navigation and what does not — in-memory stores and layout state persist, page-segment state unmounts — and name the cases where you deliberately force a document load instead.

for a principal

Own the consistency angle: an app that mixes anchors and Links unpredictably gets erratic performance and lost state. Decide where hard reloads are mandated (post-logout, auth boundary changes) and encode it in review rules or a lint rule.

## The two kinds of navigation Every route change in a web app is one of two things. A **document navigation** is what the browser does natively: it tears down the current page, requests a new HTML document, parses it, downloads and executes the scripts again, and hydrates from scratch. A **client-side navigation** keeps the same document alive and mutates the page in place, changing the URL through the History API rather than by loading a new document. A plain `<a href="/dashboard">` gives you the first. `<Link href="/dashboard">` from `next/link` gives you the second. ## What `<Link>` actually renders In the App Router, `<Link>` renders an ordinary `<a>` element into the DOM with the `href` you passed. That matters more than it sounds: middle-click, Ctrl/Cmd-click, "open in new tab", "copy link address", and crawlers all keep working, because there is a genuine anchor with a genuine URL under it. Next does not replace the anchor with a `<span>` and a click handler. What `<Link>` adds is a click listener. For a plain left-click with no modifier keys on a same-origin, in-app URL, it calls `preventDefault()` and routes the navigation through Next's client router. Modifier-clicks are left alone so the browser's native behaviour survives. ```tsx import Link from 'next/link'; export default function Nav() { return <Link href="/dashboard">Dashboard</Link>; } ``` ## What travels over the wire On a client-side navigation the router does not ask for HTML. It asks the server for the **React Server Component payload** for the destination route — a compact serialized description of the rendered server tree. Next then reconciles that payload into the tree already mounted in the browser. The App Router is segment-aware, so it only needs the segments that changed. Navigating from `/settings/profile` to `/settings/billing` when both sit under the same `app/settings/layout.tsx` fetches the new page segment; the shared layout above it is not re-requested and not re-rendered. That is why client components mounted in a shared layout — an open sidebar, a video player, a scroll container — keep their state across the navigation, while a plain anchor would destroy all of it. Prefetching amplifies this. In production builds, Next prefetches the destination when the link scrolls into the viewport and stores the result in the client-side Router Cache, so by the time the user clicks, the payload is frequently already in memory and the transition is immediate. ## What survives, and what does not Survives a `<Link>` navigation: - the JavaScript heap: module state, Zustand/Redux stores, in-memory caches - `useState` in components that are not unmounted (notably anything in a shared layout) - the loaded and parsed bundles — no re-download, no re-hydration of the whole app Does not survive: state inside the page segment being replaced, since that subtree unmounts. And nothing at all survives a plain `<a>`, because the JavaScript context itself is thrown away. ## When a plain anchor is still correct - **External or cross-origin URLs.** There is nothing for the App Router to render, so use `<a href="https://status.example.com">`. (Passing an external URL to `<Link>` degrades to a normal anchor navigation anyway, but a plain `<a>` states the intent.) - **Routes not owned by this Next app** — a legacy subpath, a file download, an endpoint that returns a PDF. - **When you genuinely want a hard reset**, for example after a logout that must discard every scrap of in-memory state. ## Common mistakes Wrapping a `<Link>` around an `<a>` is a legacy Pages Router pattern; in the App Router it produces nested anchors, which is invalid HTML. Just put the content directly inside `<Link>`. Calling `<Link>` "just an `<a>` with prefetch" misses the point — prefetch is an optimization, but the substantive difference is that the navigation never leaves the document. Finally, `<Link>` is not a replacement for programmatic navigation. When the destination depends on the outcome of some work — after a form submit, after an async check — you navigate with `useRouter().push()` from `next/navigation`, or with `redirect()` on the server.

  • Does `<Link>` still let a user open the destination in a new tab?
    Yes. `<Link>` renders a real `<a>` with the resolved `href`, and its click handler bails out on modifier-clicks and non-left buttons. Ctrl/Cmd-click, middle-click, and the context menu all fall through to native browser behaviour, so "open in new tab" and "copy link address" work exactly as they would on a plain anchor.
  • What is the `replace` prop on `<Link>` for?
    `<Link href="/step-2" replace>` swaps the current history entry instead of pushing a new one, so the back button skips the page you navigated away from. It is the declarative twin of `router.replace()`. Typical uses are wizard steps, post-login landings, and any URL you do not want the user to return to with Back.
  • How would you link to a URL outside your Next app?
    Use a plain `<a href="https://…">`. There is no route for the App Router to render, so client-side navigation has nothing to do; a normal anchor makes the intent obvious and lets you add `target` and `rel="noopener noreferrer"` where appropriate.

saying these in an interview costs you the question

  • Says Link is just an anchor with prefetching added
  • Thinks Link renders a span or button, breaking new-tab clicks
  • Claims Link fetches HTML for the destination page
  • Wraps an <a> inside <Link>, the old Pages Router pattern
  • Believes all client state survives any Link navigation

context