Client-Side Navigation with next/link
The Link component from next/link is the primary way to move between routes in a Next.js application. Instead of triggering a full browser page reload like a plain <a> tag, Link intercepts the click, swaps the page content on the client, and preserves state such as scroll position and any client-side React state that lives outside the changed route segment. This makes navigation feel instant because the browser never re-downloads the HTML document, re-parses global CSS, or re-initializes the JavaScript runtime.
Cricket analogy: Switching from Link to a bare <a> tag is like a batter walking back to the pavilion between overs instead of just crossing for a quick single — Link keeps you 'in the crease' of the app instead of restarting the whole innings.
Prefetching and Performance
In the App Router, any Link component that is visible in the viewport is automatically prefetched in the background by default, so the route's code and, for statically generated segments, its data are already available before the user even clicks. You can override this behavior with the prefetch prop: prefetch={false} disables it entirely, which is useful for routes behind authentication gates or rarely visited links where prefetching would waste bandwidth, while the default behavior in the App Router prefetches the shared layout and loading skeleton for dynamic routes without deep data fetching.
Cricket analogy: Automatic prefetching is like a wicketkeeper standing up to the stumps in anticipation of a slower ball, already positioned before the delivery arrives, rather than reacting only after the batter has played the shot.
Dynamic and Programmatic Navigation
While Link handles declarative navigation triggered by user clicks, the useRouter hook from next/navigation gives you programmatic control for cases like redirecting after a form submission or a timed logout. router.push adds a new entry to the browser history stack, while router.replace swaps the current entry, which is useful after actions like completing a checkout so users can't navigate back into a stale form. Dynamic hrefs for Link are typically built with template literals, such as href={/products/${product.id}}, letting a single Link component render correct paths for a list of items.
Cricket analogy: router.push versus router.replace is like a captain sending in a new batter after a wicket falls (push, extending the scorecard) versus a runner being substituted mid-over without altering the over count (replace, swapping in place).
'use client';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
export default function ProductRow({ product }: { product: { id: string; name: string } }) {
const router = useRouter();
async function handleQuickBuy() {
await fetch('/api/cart', { method: 'POST', body: JSON.stringify({ id: product.id }) });
router.push('/checkout');
}
return (
<div className="flex items-center gap-4">
<Link href={`/products/${product.id}`} prefetch={true}>
{product.name}
</Link>
<button onClick={handleQuickBuy}>Buy now</button>
</div>
);
}Active Links and usePathname
A very common UI pattern is highlighting the currently active navigation link, and Next.js provides usePathname from next/navigation for exactly this purpose. Because it's a client hook, usePathname must be called inside a Client Component, and it returns the current URL's path segment so you can compare it against each Link's href and conditionally apply an 'active' class. Unlike the older pages router pattern that relied on router.pathname from useRouter, usePathname in the App Router returns a plain string and re-renders only the component that calls it rather than the whole page.
Cricket analogy: usePathname is like the on-field umpire signaling which end is currently 'striker's end' — every player on the field can check that single reference to know exactly where play is focused right now.
Since Next.js 13, the legacyBehavior prop and the requirement to nest a raw <a> tag inside Link were removed. You can now pass className, onClick, and other props directly to Link itself, and it renders its own anchor tag under the hood.
Replacing Link with a plain <a href="/dashboard"> tag inside your app forces a full page reload, discarding client-side state, re-fetching the entire document, and losing the performance benefits of prefetching. Only use raw <a> tags for external URLs that leave your domain.
- Link from next/link performs client-side transitions without a full page reload.
- App Router Links visible in the viewport are prefetched automatically unless prefetch={false} is set.
- useRouter().push adds a history entry; router.replace swaps the current one without adding a new entry.
- Dynamic hrefs are built with template literals, e.g. href={
/products/${id}}. - usePathname returns the current route as a string for building active-link styling in Client Components.
- Since Next.js 13, legacyBehavior and nested <a> tags inside Link are no longer required.
- Use plain <a> tags only for links that navigate outside your application's domain.
Practice what you learned
1. What happens by default when a Link component is visible in the viewport in the App Router?
2. Which hook would you use to highlight the currently active navigation link?
3. What is the key difference between router.push and router.replace?
4. Why would you set prefetch={false} on a Link?
5. What is required to use the useRouter or usePathname hooks?
Was this page helpful?
You May Also Like
Dynamic Routes and Params
Understand how to build dynamic and catch-all routes using bracket folder naming, read route params in Server and Client Components, and pre-render them with generateStaticParams.
Loading and Error States
Learn how loading.js, error.js, and not-found.js use React Suspense and Error Boundaries to give every route automatic, granular loading and error UI.
Middleware in Next.js
Learn how middleware.ts intercepts requests on the Edge Runtime before they reach a route, and how to use it for redirects, rewrites, and authentication checks.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics