In a standard multipage application, clicking a link triggers a full browser refresh the screen goes white, the script reloads, and state is lost. Next.js uses a Client Side Router that intercepts these clicks, making transitions feel like a mobile app while maintaining the benefits of a multi page structure.
The <Link> component is the primary way to navigate in Next.js. It is an extension of the HTML <a> tag that provides Prefetching and Client-Side Navigation.
Prefetching is how Next.js makes pages feel like they load instantly. When a <Link> component enters the user's viewport (the visible part of the screen), Next.js automatically begins downloading the code and data for that route in the background.
When we click a <Link>, Next.js does not refresh the page. Instead:
We should always use <Link> for internal navigation. Use standard <a> tags only for external links.
import Link from "next/link";
export default function Navbar() {
return (
<nav>
{/* Prefetching happens automatically when this enters view */}
<Link href="/dashboard" prefetch={true}>
Dashboard
</Link>
{/* We can disable prefetching for low-priority pages */}
<Link href="/settings" prefetch={false}>
Settings
</Link>
</nav>
);
}| Feature | Standard <a> Tag | Next.js <Link> Component |
|---|---|---|
| Browser Refresh | Full Refresh (White Flash) | No Refresh (Instant Swap) |
| State Preservation | Lost (App restarts) | Maintained (Layouts stay alive) |
| Data Fetching | On Click | On Viewport (via Prefetching) |
| Bundle Impact | Downloads entire page | Downloads only the segment diff |
Prefetching is the reason why Next.js apps feel 'faster' than regular React apps. However, be careful with huge lists of links; prefetching too many routes at once can consume unnecessary data for mobile users. If a route isn't vital, use
prefetch={false}.
Next.js provides two distinct ways to navigate via code: one for the Client and one for the Server.
useRouter()When we are inside a Client Component ('use client'), we use the useRouter hook.
.push() (adds to history), .replace() (replaces current history), .refresh() (refreshes data without losing state)."use client";
import { useRouter } from "next/navigation";
export function SearchButton() {
const router = useRouter();
const handleSearch = () => {
// Logic here...
router.push("/results");
};
return <button onClick={handleSearch}>Search</button>;
}redirect()When we are in a Server Component or a Server Action, we cannot use hooks. Instead, we use the redirect() function.
import { redirect } from "next/navigation";
async function fetchUser(id: string) {
const user = await db.user.find(id);
if (!user) {
redirect("/404"); // Triggers immediately on the server
}
}Next.js has an invisible friend called the Router Cache (sometimes called the Client-side Cache). It is a temporary memory store that lives in the browser while the user is using our app.
When a user navigates to a new route, Next.js stores the Server Component Payload (the rendered UI) in this cache.
Sometimes we want the cache to be destroyed (e.g., after a user deletes an item, the Back button shouldn't show that item anymore).
router.refresh(): Clears the cache for the current route and fetches fresh data from the server.revalidatePath() or revalidateTag() automatically purges the router cache to ensure the UI stays in sync with the database.| Tool | Environment | Best Use Case |
|---|---|---|
<Link> | Client/Server | Standard UI navigation (SEO friendly). |
useRouter | Client Only | Button clicks, search bar logic. |
redirect | Server Only | Auth checks, form submission success. |
permanentRedirect | Server Only | Changing a URL structure permanently (301). |
A common mistake is trying to use
useRouterin a Server Component. Remember: Hooks are for the browser. If we are doing logic on the server (like checking a session), useredirect. If we are handling a user's click, useuseRouteror<Link>.