Linking and Navigation
Why next/link isn't just a styled anchor tag, and when to reach for the useRouter hook instead.
អាន 2 នាទី
You can navigate between Next.js pages with a plain <a href="...">, but doing so forces a full page reload — the browser throws away all JavaScript state and re-downloads everything from scratch. The <Link> component avoids that by handling navigation on the client once the app is loaded, while still degrading gracefully to a real link if JavaScript hasn't run yet.
The <Link> component
import Link from "next/link";
export default function Nav() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/blog">Blog</Link>
<Link href="/blog/hello-world">A specific post</Link>
</nav>
);
}Under the hood, <Link> renders a real <a> tag — so it's still accessible, still works with "open in new tab," and still shows up correctly to search engines. The difference is what happens on a left-click: Next.js intercepts it, fetches only the data needed for the new route, and swaps the page content in without a full reload.
Prefetching
By default, <Link> components that are visible in the viewport are automatically prefetched — Next.js quietly fetches the target route's code and cacheable data in the background before the user even clicks. That's why navigation in a well-built Next.js app often feels instant: most of the work already happened while the link was sitting on screen.
// Prefetching happens automatically. To opt a specific link out:
<Link href="/heavy-report" prefetch={false}>
View report
</Link>Navigating programmatically with useRouter
Sometimes navigation needs to happen in response to something other than a click on a link — after a form submits successfully, for example. That's what the useRouter hook is for, and it only works in a Client Component:
"use client";
import { useRouter } from "next/navigation";
export default function LogoutButton() {
const router = useRouter();
async function handleClick() {
await fetch("/api/logout", { method: "POST" });
router.push("/login");
}
return <button onClick={handleClick}>Log out</button>;
}router.push("/login") navigates like a link click would. router.replace("/login") does the same but doesn't add a new entry to browser history — useful after a redirect you don't want the user to "back" into. router.refresh() re-fetches the current route's server data without a full reload, which matters once you start mutating data (covered in the Server Actions lessons).
<Link> vs. useRouter: which one
Default to <Link> for anything a user clicks to go somewhere — it's simpler, more accessible, and gets prefetching for free. Reach for useRouter only when navigation needs to happen from code: after an async action completes, based on a condition, or from inside an event handler that isn't a simple click on a link. Mixing the two up — building a full interactive useRouter-driven navigation for what's really just a link — adds JavaScript and complexity for no benefit.