next/link

Linking and Navigating with next/link

next/link renders a real <a> element — it has since Next.js 13 10,514 , when the nested <a> child was dropped — so middle-click, Ctrl-click and crawler discovery keep working. It adds a click handler that cancels the browser's navigation and hands the work to the router, plus an IntersectionObserver that prefetches the destination while the link is on screen.

app/page.jsJavaScript
import Link from 'next/link'
export default function Home() {
  return <ul>
    <li><Link href="/reports">Reports</Link></li>
    <li><Link href="/dashboard" prefetch={false}>Dashboard (no prefetch)</Link></li>
  </ul>
}

The root layout adds a nav bar and a Clock, a Client Component counting seconds with setInterval, which answers the question this section turns on: does a navigation throw the page away? Clicking the Reports link in Chrome 1 produced one network request and left the clock running.

Output of 12
  3086ms  rsc  /reports?_rsc=O7B9SOatjc8aCNiq  rsc=1  prefetch=-
clock before click : layout alive for 5s
clock after  click : layout alive for 7s

No document, no stylesheet, no bundle — one GET carrying the header rsc: 1, which asks for the React 7,897 Server Component payload of the part of the tree that changed. Swap that <Link> for a plain <a href="/reports"> and the same click costs a document, every asset in the <head>, a new React root and a clock that restarts at zero.

Four props change how the click behaves.

The props that change how <Link> navigates
Prop Default Effect
replace false Replaces the history entry instead of pushing one
scroll true Scrolls to the page element if it is off screen
prefetch null false disables it; see Prefetching
onNavigate none Runs only on same-origin client-side navigations

scroll deserves care. The default is not "jump to the top": the router keeps the scroll position while the new page element is still in the viewport, and skips sticky and fixed elements when hunting for a scroll target, so content can land behind a sticky header. The fix is CSS — html { scroll-padding-top: 64px; }. onNavigate fires only when the router will handle the click, which makes it the place for an unsaved-changes prompt: e.preventDefault() on its event cancels the navigation, while a Ctrl-click, an external URL and a download link bypass it.