Active Link Styling
Almost every navigation bar needs to highlight the link that corresponds to the page the user is currently on — a bold label, an underline, a different background color. The App Router doesn't give
<Link> a built-in active prop for this, but Next.js gives you exactly what you need to build it yourself: the usePathname() hook from next/navigation, which returns the current URL's path as a plain string on every render.Note
This page assumes you're already comfortable with the
<Link> component and basic navigation. If not, visit the Linking & Navigation and usePathname pages first — this page focuses specifically on the "is this the active route?" pattern built on top of it.The core idea: compare pathname to href
Once you have the current pathname as a string, "is this link active?" becomes a simple string comparison against each link's
href. Wrap that comparison in a small helper component so every nav item gets consistent behavior.components/NavLink.tsx
TSX
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
type NavLinkProps = {
href: string
children: React.ReactNode
}
export default function NavLink({ href, children }: NavLinkProps) {
const pathname = usePathname()
const isActive = pathname === href
return (
<Link
href={href}
// aria-current tells assistive tech this is the current page,
// and doubles as a CSS hook: a[aria-current="page"] { ... }
aria-current={isActive ? 'page' : undefined}
className={isActive ? 'nav-link nav-link--active' : 'nav-link'}
>
{children}
</Link>
)
}A full navigation bar just renders a handful of these, each pointing at a different route:
components/NavBar.tsx
TSX
import NavLink from './NavLink'
export default function NavBar() {
return (
<nav>
<NavLink href="/">Home</NavLink>
<NavLink href="/blog">Blog</NavLink>
<NavLink href="/about">About</NavLink>
</nav>
)
}Exact match vs. nested-route match
Strict equality (
pathname === href) works well for top-level links like Home or About, but it breaks down for section links that should stay "active" while the user is anywhere inside that section. If the user navigates to /blog/my-first-post, the Blog link's href is /blog — not an exact match — but you probably still want it highlighted. For that, switch to a prefix check with pathname.startsWith(href).A naive startsWith check matches too broadly
pathname.startsWith(href) compares raw strings, not path segments. If you have a /blog link and the site also has a completely unrelated /blog-archive route, then visiting /blog-archive makes "/blog-archive".startsWith("/blog") evaluate to true — the Blog link lights up on a page it has nothing to do with. The fix is to also check that the character right after the prefix is a path separator (or that there's nothing after it at all).components/NavLink.tsx — segment-safe matching
TSX
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
type NavLinkProps = {
href: string
children: React.ReactNode
exact?: boolean
}
export default function NavLink({ href, children, exact }: NavLinkProps) {
const pathname = usePathname()
const isActive = exact
? pathname === href
: pathname === href || pathname.startsWith(`${href}/`)
return (
<Link
href={href}
aria-current={isActive ? 'page' : undefined}
className={isActive ? 'nav-link nav-link--active' : 'nav-link'}
>
{children}
</Link>
)
}Appending a trailing slash before comparing (
`${href}/` as the prefix) guarantees the match only succeeds at a real path boundary — /blog/ is a prefix of /blog/my-first-post, but it is not a prefix of /blog-archive. Use the exact prop for links that should only ever match their own exact route, like Home pointing at / (where a prefix check would match literally every route on the site).Note
This pattern requires a Client Component.
usePathname() is a hook, and hooks only run in components that opt into the client with 'use client' — there is no server-side equivalent, because "the current route the browser is displaying" is inherently client-side navigation state. In practice this is fine: keep the nav link itself small and client-side while the rest of the page around it stays a Server Component.usePathname()returns the current URL path as a plain string, re-evaluated on every navigation.Exact match (
pathname === href) suits top-level links; prefix match suits section links with nested routes.Guard prefix matches at a path boundary (e.g. compare against
`${href}/`) to avoid false positives like/blogmatching/blog-archive.usePathnameis a hook, so any component using it must be a Client Component.