In the previous tutorial, you learned how Next.js layouts and pages work. Once an application has several routes, users need clear ways to move between them. This guide covers declarative links, dynamic destinations, client-side transitions, prefetching, programmatic navigation, server redirects, and active navigation.
The examples use TypeScript and the current Next.js 16 App Router. If the relationship between route folders and URLs is still unfamiliar, first understand the Next.js project structure.
How Navigation Works in Next.js
App Router uses folders and page.tsx files to create destinations. Imagine this small project:
app/
|-- page.tsx # /
|-- about/page.tsx # /about
|-- blog/page.tsx # /blog
`-- contact/page.tsx # /contactNavigation does not create those routes; it connects the route UI already defined by the file system. A link points to a URL, the Next.js router resolves it, and the matching page renders inside its layouts.
//about/blog/contact<Link href="/about">Next.js Router/aboutAbout PageCreating Links with next/link
Link is the primary way to move between routes. It extends the behavior of an HTML anchor with Next.js client transitions and prefetching. The required href prop identifies the destination:
import Link from 'next/link'
export default function HomePage() {
return (
<main>
<h1>Home</h1>
<Link href="/about">About</Link>
</main>
)
}A navigation component with static links does not need 'use client'. Keep it as a Server Component unless it needs state, event handlers, browser APIs, or client hooks:
import Link from 'next/link'
export function Navigation() {
return (
<nav aria-label="Main navigation">
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/blog">Blog</Link>
<Link href="/contact">Contact</Link>
</nav>
)
}The nav landmark tells assistive technology that these links form a navigation group. Descriptive link labels also help keyboard users, screen-reader users, and search crawlers understand each destination.
Next.js <Link> vs HTML <a>
| Use case | Recommended approach | Reason |
|---|---|---|
| Internal Next.js route | <Link> | Client transition and automatic production prefetch behavior |
| External website | <a href="https://..."> | Normal browser navigation is appropriate |
| Email or phone | mailto: or tel: anchor | Hands the action to the relevant application |
| Download | Anchor, often with download | Communicates a resource action instead of an app route |
| Intentional document reload | Anchor | Uses the browser's full navigation behavior |
Do not wrap a modern Link around another anchor. Current Next.js renders the underlying anchor for you. For external links that open a new tab, use meaningful text and add rel="noopener noreferrer" with target="_blank".
Understanding Client-Side Navigation
A traditional navigation replaces the current browser document. With a Next.js Link, the framework performs a client-side transition: shared layouts can stay mounted while the active page content changes. This preserves useful interface state and avoids an unnecessary full document reload.
Client-side does not mean server-free. Pages and layouts are Server Components by default, and a later navigation can still request a React Server Component payload or route resources from the server. Prefetching and streaming determine how much is already available when the user clicks.

Traditional navigation
- Link selected
- Browser requests document
- Current document unloads
- New document renders
Next.js Link
- Link selected
- Router starts transition
- Shared layouts stay
- Route UI updates
Linking to Dynamic Routes
A dynamic segment such as app/blog/[slug]/page.tsx can render many post URLs. The link still receives a normal final URL:
<Link href="/blog/nextjs-layouts">
Next.js Layouts
</Link>For a list, interpolate each trusted slug into the path:
import Link from 'next/link'
const posts = [
{ slug: 'nextjs-layouts', title: 'Next.js Layouts' },
{ slug: 'nextjs-navigation', title: 'Next.js Navigation' },
]
export default function BlogPage() {
return posts.map((post) => (
<Link key={post.slug} href={`/blog/${post.slug}`}>
{post.title}
</Link>
))
}The backticks create a template literal, and ${post.slug} inserts the current value. Validate slugs from external data and confirm that generated URLs resolve to real pages. The earlier guide to creating routes with App Router explains why the bracketed folder matches these URLs.
How Next.js Prefetching Works
Prefetching loads useful route resources in the background before navigation. In production, Next.js automatically considers Link destinations when links enter the viewport. The goal is to reduce the work that remains after the click.
Static routes can be fully prefetched. For dynamic routes, prefetching is skipped unless Next.js can partially prefetch a shared shell and loading state. Adding loading.tsx creates a streaming boundary, allowing shared layouts and fallback UI to arrive ahead of the slower route content.
- 1Link enters viewportProduction build
- 2Next.js may prefetchFull or partial route
- 3User selects linkIntent becomes certain
- 4Transition beginsReuse available resources
You can disable automatic prefetching for a particular link:
<Link href="/large-report" prefetch={false}>
Open report
</Link>This can reduce speculative work in an enormous or infinite list, but it shifts all loading to the click. Keep the default unless measurement reveals a reason to change it. Also test production behavior: automatic prefetching is not active in development in the same way.
Programmatic Navigation with useRouter
Use a normal Link when the interface simply offers a destination. Use useRouter when navigation follows client-side logic—for example, a button that opens a dashboard after a completed interaction.
'use client'
import { useRouter } from 'next/navigation'
export function DashboardButton() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Open Dashboard
</button>
)
}The event handler and hook require a Client Component, so the file begins with 'use client'. Keep this boundary around the interactive component rather than turning an entire layout into client code.
Navigation hooks such as useRouter and usePathname run in Client Components. If this boundary is new, continue with our guide to Server and Client Components in Next.js.
router.push(href)navigates and adds a browser history entry.router.replace(href)navigates without adding a new history entry.router.back()androuter.forward()move through browser history.router.refresh()requests and reconciles the current route without losing unaffected client or browser state.router.prefetch(href)manually prefetches a route when a measured use case requires it.
Never pass an unsanitized user-controlled URL to router.push or router.replace. Dangerous schemes such as javascript: can execute in the page. Prefer known internal destinations or validate both the path and allowed origin.

Redirecting Users
A redirect is appropriate when the current route should not render for this request—for example, an unauthenticated user reaches a protected page. Import redirect from next/navigation and call it during rendering:
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const user = await getCurrentUser()
if (!user) {
redirect('/login')
}
return <h1>Dashboard</h1>
}redirect() ends rendering by throwing a framework-handled redirect, so call it outside a try block that could accidentally catch it. It is available during Server Component rendering, Server Functions, and Route Handlers. In a Client Component event handler, use useRouter instead. Use permanentRedirect for a genuinely permanent canonical move, and configuration redirects when an incoming path should always map elsewhere.
<Link>Declarative and accessibleuseRouterEvent-driven navigationredirect()Server routing decision| Situation | Prefer |
|---|---|
| Normal navigation link | <Link> |
| Navigation after a client action | useRouter where justified |
| Server-side access or mutation decision | redirect() or permanentRedirect() |
| Active navigation UI | usePathname() |
| Fixed incoming-path mapping | redirects in Next.js config |
Active Navigation with usePathname
usePathname reads the current URL pathname. It is a client hook, so isolate it in the smallest useful navigation component. Exact equality works for top-level pages; nested sections usually need a segment-aware match.
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
const links = [
{ href: '/', label: 'Home' },
{ href: '/blog', label: 'Blog' },
{ href: '/about', label: 'About' },
]
export function NavLinks() {
const pathname = usePathname()
return links.map((link) => {
const active = link.href === '/'
? pathname === '/'
: pathname === link.href || pathname.startsWith(`${link.href}/`)
return (
<Link
key={link.href}
href={link.href}
className={active ? 'nav-link is-active' : 'nav-link'}
aria-current={active ? 'page' : undefined}
>
{link.label}
</Link>
)
})
}The explicit root check prevents every path from matching /. The second condition treats /blog as active on a nested URL such as /blog/nextjs-navigation without incorrectly matching /blogger. If rewrites change the browser pathname, keep pathname-dependent UI small and test hydration carefully.
Building an Accessible Navigation Bar
For a production header, render the logo and noninteractive shell on the server, then place only the active-link list or menu button in a focused Client Component. Use a labelled nav, visible focus styles, sufficient contrast, and a real button for opening a mobile menu.
- Make every destination reachable with the keyboard.
- Keep link text descriptive instead of repeating “click here.”
- Use
aria-current="page"in addition to color for the active item. - Do not put a button inside a link or a link inside a button.
- Close a mobile menu after navigation and restore a sensible focus position.
- Verify every internal URL in both desktop and mobile menus.
Common Navigation Mistakes
- Using anchors for every internal route. You give up Next.js client transitions and automatic prefetch behavior.
- Using
useRouterfor visible links. A real link is more semantic and supports expected browser behaviors such as opening in a new tab. - Adding
'use client'to the whole application. Keep the boundary close touseRouter,usePathname, or interactive state. - Matching active links with a loose prefix. A check like
pathname.startsWith('/blog')also matches/blogger; include a segment boundary. - Generating routes without validation. Confirm CMS slugs and internal destinations so cards do not lead to missing pages.
- Passing user input directly to router methods. Restrict navigation to trusted local paths or validate protocols and origins.
- Disabling prefetch everywhere. Measure resource use and navigation latency before changing the default.
- Simulating a redirect in an effect. Prefer server redirect APIs when the server already knows the destination.
Next.js Navigation Best Practices
- Prefer
Linkfor standard internal navigation and descriptive anchor text for related articles. - Reserve programmatic navigation for an actual action or interaction.
- Use redirects for redirect logic, not a client effect that briefly displays the wrong page.
- Keep active-state and menu client boundaries small.
- Add
aria-currentand test focus, keyboard movement, and mobile menus. - Understand prefetching before disabling it, especially for dynamic routes.
- Add
loading.tsxwhere a dynamic route needs immediate feedback and partial prefetching. - Verify version-sensitive navigation behavior against the official documentation.
Navigation, SEO, and Internal Links
Useful internal links help readers discover related tutorials, reveal the site's structure, and give crawlers paths to published pages. They also express context: a link from route organization to layouts, then to navigation, shows how the concepts build on one another. This does not mean that adding arbitrary links automatically improves rankings. Each link should answer a likely next question.
Frequently Asked Questions
How do I link to another page in Next.js?
Import Link from next/link and give it the internal route through href, such as <Link href="/about">About</Link>.
What is the Next.js Link component?
Link is the primary component for navigation between Next.js routes. It renders an anchor and adds client-side transitions and production prefetching.
Should I use Link or an anchor in Next.js?
Prefer Link for internal application routes. A normal anchor is appropriate for external sites, email, telephone, downloads, or intentional document navigation.
What is useRouter in Next.js?
useRouter is a Client Component hook from next/navigation that performs navigation from event handlers and other interactive logic.
What is the difference between Link and useRouter?
Link describes a destination in the interface and remains the default choice. useRouter starts navigation from client-side logic, often after an action.
How does Next.js prefetch links?
In production, Next.js can fetch route resources when a Link enters the viewport. Static routes can be fully prefetched; dynamic routes may be skipped or partially prefetched when a loading boundary exists.
How do I redirect a user in Next.js?
Call redirect from next/navigation during server rendering, in a Server Function, or in a Route Handler. Use useRouter for navigation inside a Client Component event handler.
How do I highlight the active link in Next.js?
Read the pathname with usePathname in a focused Client Component, compare it with each route, and expose the result with styling and aria-current="page".
Official Resources
Check the current Next.js Linking and Navigating guide. The official API references explain Link, useRouter, redirect, and usePathname in more depth.
Next Steps
You can now connect routes with semantic links, explain why transitions feel fast, choose programmatic navigation only when interaction requires it, redirect on the server, and expose an accessible active state. Next, learn how the Server and Client Component boundary applies to the useRouter and usePathname examples.
