Use the Next.js Link component for fast page changes, learn how prefetching works, navigate in code with useRouter, and highlight active links with usePathname.

Bishal Biswas
Self Learner
What you will learn
Book Nook has pages, a root layout and a books layout. But its menu uses plain <a> tags, and every click reloads the whole page. In this lesson you will make navigation fast and smooth:
- swap
<a>for the Next.js<Link>component - understand client-side navigation and prefetching
- move the menu into its own
components/Header.js - highlight the current page with
usePathname - navigate from code, like a “Continue shopping” button, with
useRouter
You need the layouts from Pages and Layouts.
Why not a plain a tag?
Click Books in your current menu and watch the browser’s reload button: it flickers. A plain <a href="/books"> makes the browser throw the whole page away and load a new one from scratch. That means:
- the header, footer and fonts are downloaded and drawn again
- anything typed into a layout (a search box, say) is lost
- the screen may flash white for a moment
ESLint already warns you about it. Run npm run lint and you see:
error Do not use an `<a>` element to navigate to `/books/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages @next/next/no-html-link-for-pages
Step 1: use Link
<Link> is a React component from next/link. It still renders a normal <a> tag in the HTML (so it works for search engines, right-click “open in new tab”, and screen readers), but it adds two powers:
- Client-side navigation. When you click, Next.js swaps only the part of the page that changed, without a full reload. Layouts stay on screen and keep their state.
- Prefetching. Next.js loads linked pages in the background before you click, so the click feels instant.
Using it is almost the same as <a>:
import Link from 'next/link';
<Link href="/books">Books</Link>
You can pass the usual <a> attributes too, like className. They are passed down to the real <a> tag:
<Link href="/books" className="font-bold underline">
Books
</Link>
Step 2: move the menu into a Header component
The menu will grow, so let us give it its own file. Create a components folder at the top of the project (next to app, not inside it) and add Header.js:
// components/Header.js
import Link from 'next/link';
export default function Header() {
return (
<header className="border-b px-6 py-4">
<nav className="flex gap-6">
<Link href="/" className="font-bold">Book Nook</Link>
<Link href="/books">Books</Link>
<Link href="/about">About</Link>
<Link href="/cart">Cart</Link>
</nav>
</header>
);
}
Now use it in the root layout. Thanks to the @/ alias you set up with create-next-app, the import works from anywhere:
// app/layout.js (only the changed parts)
import Header from "@/components/Header";
export default function RootLayout({ children }) {
return (
<html
lang="en"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>
<body className="min-h-full flex flex-col">
<Header />
<main className="mx-auto w-full max-w-3xl flex-1 p-8">{children}</main>
<footer className="border-t px-6 py-4 text-sm text-zinc-500">
Book Nook: a small shop for big stories.
</footer>
</body>
</html>
);
}
Click around. The pages change without the white flash, and the header never redraws. Do the same in app/books/layout.js: import Link and replace the two <a> tags in the side menu.
How client-side navigation works
Here is what happens when you click a <Link>:
- Next.js stops the browser’s normal full page load.
- It asks the server only for what is new: the page for the new URL (often it already has it, thanks to prefetching).
- It swaps that page into the layout. The layouts around it stay untouched.
- It updates the address bar and the browser history, so the Back button still works.
- It scrolls to the top of the new page.
It is like changing the photo in a picture frame instead of buying a new frame every time.
Prefetching
Prefetching means loading a page in the background before the user clicks on it. Next.js prefetches a <Link> automatically when it scrolls into view on the screen (or when you hover over it). By the time you click, the page is often already in the browser.
Two things to know:
- Prefetching only happens in production (
npm run buildandnpm start). In development (npm run dev) it is turned off, so pages can feel slower there than they really are. - Book Nook has
partialPrefetching: trueinnext.config.mjs. With it, each link prefetches the page’s App Shell: its static parts and cached content. Anything that has to be made fresh for each visitor streams in after the click.
You can control prefetching per link with the prefetch prop:
// Default: Next.js decides (with Partial Prefetching, the App Shell is prefetched)
<Link href="/books">Books</Link>
// Prefetch the full page, including content that depends on the URL
<Link href="/books" prefetch={true}>Books</Link>
// Never prefetch, for example in a very long list of links
<Link href="/books" prefetch={false}>Books</Link>
For almost every link, leave the default. You will see how prefetching and caching work together in Partial Prerendering: Static Shells with Dynamic Parts.
Other useful Link props
// Replace the current history entry instead of adding one.
// The Back button then skips this page.
<Link href="/login" replace>Sign in</Link>
// Do not scroll to the top after navigating
<Link href="/books" scroll={false}>Books</Link>
// Build a link with a query string: /books?sort=price
<Link href={{ pathname: '/books', query: { sort: 'price' } }}>
Cheapest first
</Link>
Links can also be built from data with a template string, which you will do for every book in Dynamic Routes and params:
<Link href={`/books/${book.id}`}>{book.title}</Link>
Links to other websites
For links that leave your site, use a normal <a>. There is nothing to prefetch, and <Link> is meant for pages in your own app:
<a href="https://nextjs.org" target="_blank" rel="noopener noreferrer">
Built with Next.js
</a>
Step 3: highlight the active link
It is nice when the menu shows where you are. To do that, the header must know the current URL. Next.js gives you a hook for it: usePathname from next/navigation. It returns the pathname, the part of the URL after the domain, like /books/bestsellers.
There is one catch. Hooks like usePathname only work in Client Components, components that also run in the browser. You make a file a Client Component by putting 'use client' at the very top. (You will learn all about this in Client Components and the use client Directive.)
Update components/Header.js:
// components/Header.js
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
const links = [
{ href: '/books', label: 'Books' },
{ href: '/about', label: 'About' },
{ href: '/cart', label: 'Cart' },
];
export default function Header() {
const pathname = usePathname();
return (
<header className="border-b px-6 py-4">
<nav className="flex gap-6">
<Link href="/" className="font-bold">Book Nook</Link>
{links.map((link) => {
// "/books" should also be active on "/books/bestsellers"
const isActive = pathname.startsWith(link.href);
return (
<Link
key={link.href}
href={link.href}
className={isActive ? 'font-semibold underline' : 'text-zinc-600'}
>
{link.label}
</Link>
);
})}
</nav>
</header>
);
}
How it works:
'use client'makes this a Client Component, so it can use the hook.usePathname()gives the current path, for example/books/bestsellers.pathname.startsWith(link.href)istruefor/booksand anything below it, so Books stays highlighted on every books page.- The links are stored in an array and turned into
<Link>elements withmap, so adding a menu item later is one line.
When you click a link, the header re-renders with the new pathname, and the underline moves. The rest of the layout stays put.
Step 4: wrap the header in Suspense
There is one more step, and it matters for Book Nook because Cache Components is turned on.
With Cache Components, Next.js tries to prepare as much of every page as possible ahead of time. But the pathname of a page like /books/dune is only known when a real visitor asks for it. So Next.js wants you to mark the part that reads it with React’s <Suspense> component: a wrapper that shows a stand-in (the fallback) until its contents are ready.
Right now all your pages have fixed URLs, so nothing breaks yet. But in the very next lesson you add your first dynamic page, and without <Suspense> both the dev overlay and npm run build stop with:
Error: Route "/books/[id]": Next.js encountered URL data `usePathname()` in a Client Component outside of `<Suspense>`.
This blocks prerendering because the value is only available at runtime.
So add it now. In app/layout.js:
// app/layout.js (only the changed parts)
import { Suspense } from "react";
import Header from "@/components/Header";
// ...inside <body>, replace <Header /> with:
<Suspense fallback={<header className="border-b px-6 py-4 font-bold">Book Nook</header>}>
<Header />
</Suspense>
The fallback is a plain header with just the shop name. On pages that are prepared ahead of time, visitors never see it. On dynamic pages, it shows for a split second while the real header arrives. You will learn much more about <Suspense> in Loading UI, Suspense and Streaming.
Step 5: navigate from code with useRouter
Sometimes you need to change pages from code instead of a link, for example after a button click. The useRouter hook from next/navigation gives you a router object for that. Like usePathname, it only works in a Client Component.
Let us add “Continue shopping” and “Go back” buttons to the cart. Create components/CartActions.js:
// components/CartActions.js
'use client';
import { useRouter } from 'next/navigation';
export default function CartActions() {
const router = useRouter();
return (
<div className="mt-6 flex gap-4">
<button
type="button"
onClick={() => router.push('/books')}
className="rounded bg-black px-4 py-2 text-white"
>
Continue shopping
</button>
<button
type="button"
onClick={() => router.back()}
className="rounded border px-4 py-2"
>
Go back
</button>
</div>
);
}
And use it in the cart page:
// app/cart/page.js
import CartActions from '@/components/CartActions';
export default function CartPage() {
return (
<section>
<h1 className="text-3xl font-bold">Your cart</h1>
<p className="mt-4">Your cart is empty.</p>
<CartActions />
</section>
);
}
The cart page stays a Server Component. Only the small part with buttons is a Client Component. That is a good habit: keep 'use client' for the pieces that really need the browser.
Here are the router methods you will use most:
| Method | What it does |
|---|---|
router.push('/books') |
Go to a page and add it to the history (like clicking a link). |
router.replace('/login') |
Go to a page without adding a history entry. |
router.back() |
Go back one page, like the browser’s Back button. |
router.forward() |
Go forward one page. |
router.refresh() |
Ask the server for fresh data for the current page, keeping state. |
router.prefetch('/cart') |
Prefetch a page by hand. |
Prefer <Link> whenever you can. Links are real <a> tags, so they work with right-click, middle-click, keyboard navigation and search engines. Use useRouter only when there is no link to click, like after a form is saved.
Common mistakes
Importing useRouter from next/router
Old tutorials (and the Pages Router) use next/router. In the App Router you must import from next/navigation. With the wrong import, the page crashes with:
Error: NextRouter was not mounted. https://nextjs.org/docs/messages/next-router-not-mounted
Change the import to import { useRouter } from 'next/navigation';.
Using usePathname without ‘use client’
If you call usePathname (or useRouter) in a Server Component, the build stops:
Error: You're importing a module that depends on `usePathname` into a React Server Component module. This API is only available in Client Components. To fix, mark the file (or its parent) with the `"use client"` directive.
Add 'use client'; as the very first line of the file.
Forgetting Suspense around the header
Without the <Suspense> from Step 4, the first dynamic route you add fails with “encountered URL data usePathname() in a Client Component outside of <Suspense>”. Wrap the component that calls usePathname in <Suspense>.
Putting an a tag inside Link
Very old Next.js code wrote <Link href="/books"><a>Books</a></Link>. Today <Link> already renders the <a>, so this creates a link inside a link, and React reports <a> cannot be a descendant of <a>. Put the text directly inside <Link>.
Thinking prefetching is broken in development
In npm run dev, clicks may wait for the server because prefetching is off. Run npm run build and npm start to see the real, instant navigation.
Quick recap
- Use
<Link href="...">fromnext/linkfor pages in your app, and plain<a>for other websites. - Link gives client-side navigation (only the page swaps, layouts stay) and prefetching (pages load before you click, in production).
- With
partialPrefetchingon, links prefetch each page’s App Shell. Useprefetch={false}to turn it off, orprefetch={true}for the full page. usePathname()fromnext/navigationreturns the current path. Use it in a Client Component ('use client') to highlight active links.- With Cache Components, wrap a component that reads the URL, like the header, in
<Suspense>. useRouter()fromnext/navigationgivespush,replace,back,forward,refreshandprefetch. Prefer<Link>when there is something to click.
// components/Header.js: the Book Nook header with active links
'use client'; // needed for the usePathname hook
import Link from 'next/link';
import { usePathname } from 'next/navigation';
const links = [
{ href: '/books', label: 'Books' },
{ href: '/about', label: 'About' },
{ href: '/cart', label: 'Cart' },
];
export default function Header() {
const pathname = usePathname(); // for example "/books/bestsellers"
return (
<header className="border-b px-6 py-4">
<nav className="flex gap-6">
<Link href="/" className="font-bold">Book Nook</Link>
{links.map((link) => {
// "/books" stays active on every page below it
const isActive = pathname.startsWith(link.href);
return (
<Link
key={link.href}
href={link.href}
className={isActive ? 'font-semibold underline' : 'text-zinc-600'}
>
{link.label}
</Link>
);
})}
</nav>
</header>
);
}
// --- in app/layout.js: wrap the header in Suspense (Cache Components needs it)
// import { Suspense } from "react";
// import Header from "@/components/Header";
// <Suspense fallback={<header className="border-b px-6 py-4 font-bold">Book Nook</header>}>
// <Header />
// </Suspense>
// --- components/CartActions.js: navigate from code
// const router = useRouter(); // from 'next/navigation'
// router.push('/books'); router.back();
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready