usePathname()

Read the current URL pathname inside a Client Component.

Signature
import { usePathname } from 'next/navigation'

const pathname = usePathname()
  • Added in Next.js v13.0.0

Return value

stringThe pathname of the current URL, without the query string or hash: '/' for /, '/dashboard' for /dashboard?v=2, '/blog/hello-world' for /blog/hello-world. In a project that also has a pages directory the return type widens to string | null.

Errors

Calling it from a Server Component errors — reading the current URL on the server is deliberately unsupported so layout state survives navigations.

Changelog

  • PHP v13.0.0usePathname introduced.

usePathname is the App Router's answer to router.pathname. It returns the path portion of the current URL as a plain string and re-renders the component on every navigation, which makes it the standard way to mark a link as active.

Reach for it when the UI depends on where the user is rather than on route data: active navigation states, an analytics page-view effect, a breadcrumb, or a layout that hides a sidebar on one route. If you only need the segment directly below the layout you are in, useSelectedLayoutSegment is cheaper to reason about, because it already ignores anything deeper in the tree.

What trips people up

  • It is client-only. A Server Component cannot read the URL; that restriction exists so a shared layout does not have to re-render on navigation. Push the hook down into a small 'use client' component and import that into the layout.
  • Rewrites cause hydration mismatches. A statically prerendered page emits HTML for the source path; if the visitor arrived through a rewrite in next.config or a Proxy file, the browser path differs and React complains. Render a stable fallback and set the real value in an effect.
  • Prefix matching. pathname === '/dashboard' will not match /dashboard/invoices. Decide explicitly whether an active link should match exactly or by prefix.
  • With cacheComponents enabled, the hook suspends on routes whose dynamic params are not known at build time, so it needs a Suspense boundary above it.

Example

Example · typescript
'use client'

import Link from 'next/link'
import { usePathname } from 'next/navigation'

const links = [
  { href: '/dashboard', label: 'Overview' },
  { href: '/dashboard/invoices', label: 'Invoices' },
  { href: '/dashboard/customers', label: 'Customers' },
]

export default function NavLinks() {
  const pathname = usePathname()

  return (
    <nav>
      {links.map((link) => {
        // '/dashboard' must match exactly, the others also match children
        const isActive =
          pathname === link.href ||
          (link.href !== '/dashboard' && pathname.startsWith(`${link.href}/`))

        return (
          <Link
            key={link.href}
            href={link.href}
            aria-current={isActive ? 'page' : undefined}
          >
            {link.label}
          </Link>
        )
      })}
    </nav>
  )
}

When to use it

  • Add aria-current="page" to the nav link matching the current route.
  • Fire an analytics page view whenever the pathname or search params change.
  • Hide a marketing header on every route under /app.
  • Build a breadcrumb trail by splitting the pathname on slashes.

Frequently asked questions

How do I get the current URL path in a Next.js Server Component?

You cannot — usePathname is Client-Component only by design, so shared layouts are not re-rendered on navigation. Read the path from a small 'use client' child, or use the params and searchParams props of page.js on the server.

Does usePathname include the query string?

No. /dashboard?v=2 returns '/dashboard'; the hash and query are stripped. Use useSearchParams for the query string.

Why does usePathname return null in my app?

That happens only in projects that also contain a pages directory, where the router may not be initialized yet — for example on fallback routes or automatically statically optimized pages. Next.js widens the return type to string | null in that case.

More examples

Running an effect on every route change

pathname and searchParams are new values on each navigation, so the effect runs once per route change and reports the full URL. Because useSearchParams is in the dependency list, this component must sit inside a Suspense boundary on any prerendered route.

Example · typescript
'use client'

import { useEffect } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'

export function PageViewTracker() {
  const pathname = usePathname()
  const searchParams = useSearchParams()

  useEffect(() => {
    const query = searchParams.toString()
    const url = query ? `${pathname}?${query}` : pathname
    window.analytics?.page(url)
  }, [pathname, searchParams])

  return null
}

Avoiding a hydration mismatch behind a rewrite

The server renders an empty span, so the prerendered HTML matches whatever the browser hydrates, even when a rewrite makes the browser URL differ from the built path. The real pathname appears immediately after mount, at the cost of one frame showing the fallback.

Example · typescript
'use client'

import { useEffect, useState } from 'react'
import { usePathname } from 'next/navigation'

export default function PathnameBadge() {
  const pathname = usePathname()
  const [clientPathname, setClientPathname] = useState('')

  useEffect(() => {
    setClientPathname(pathname)
  }, [pathname])

  return (
    <p>
      Current pathname: <span>{clientPathname}</span>
    </p>
  )
}

Mounting a client hook inside a Server Component layout

The layout stays a Server Component and only the NavLinks subtree ships to the browser. This is the standard shape for every hook on this page: the client boundary wraps the smallest component that actually needs the URL.

Example · typescript
// app/dashboard/layout.tsx  (Server Component — no 'use client')
import NavLinks from '@/app/components/nav-links'

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <section>
      <aside>
        <NavLinks />
      </aside>
      {children}
    </section>
  )
}

Discussion

  • Be the first to comment on this lesson.