How does navigation work — Link vs router.push — and what does prefetching do?
Take a moment.
Say your answer out loud or write it down first — retrieval is what makes it stick. Reveal when you're ready.
Short answer
<Link> is the default: client-side transitions, prefetching viewport-visible routes, preserving scroll/state of shared layouts. router.push programmatically navigates (after mutations). Prefetching downloads route payloads ahead of clicks making transitions instant.
Detailed explanation
Link renders an anchor (SEO-friendly, middle-click works) but intercepts clicks for soft navigation: only changed segments fetch/render, layouts stay mounted, browser history updates correctly. Prefetching starts automatically when links scroll into viewport — static routes fully prefetch; dynamic ones fetch the shared layout/shell, streaming the rest on click. That's why App Router navigations feel instant. router.push/replace/back/refresh handle imperative cases: navigating after form success, programmatic redirects post-login, forcing refresh after mutations (router.refresh() re-fetches server components preserving client state — the idiomatic post-mutation update alongside revalidation). usePathname/useSearchParams power active-link styling and query-driven UI. Pitfalls: plain <a> tags lose prefetching and trigger full reloads; prefetch={false} disables eager fetching for logout links etc.; nested same-route push needs care to avoid duplicate history entries.
Example
'use client';
import Link from 'next/link';
import { useRouter, usePathname } from 'next/navigation';
function Nav() {
const router = useRouter();
const pathname = usePathname();
async function handleLogout() {
await fetch('/api/logout', { method: 'POST' });
router.push('/'); // imperative, after side effect
router.refresh(); // re-run server components
}
return (
<nav>
<Link href="/products" className={pathname === '/products' ? 'active' : ''}>Products</Link>
<button onClick={handleLogout}>Log out</button>
</nav>
);
}Interview tip
Explain prefetching behavior (viewport-triggered, static vs dynamic difference) — most candidates know Link but not how prefetching changes perceived performance.
Common mistakes
- Using raw <a> tags internally, losing client navigation.
- Not knowing router.refresh() exists for post-mutation server updates.