usePathname in Next.js and its companion hook useSearchParams read two different parts of the current URL from inside a Client Component. The first returns the route itself, and the second returns everything after the question mark.
// app/nav/current-path.tsx
"use client";
import { usePathname } from "next/navigation";
export default function CurrentPath() {
const pathname = usePathname();
return <p>You are on {pathname}</p>;
}Visit the blog route with a sort parameter in the query string, and this component still renders "You are on /blog". usePathname strips off the query string and any hash, returning only the path itself, and it updates automatically on every navigation.
Reading the query string
useSearchParams returns a read-only version of the standard URLSearchParams interface, so a value is read with its get method instead of plain object access.
// app/nav/sort-label.tsx
"use client";
import { useSearchParams } from "next/navigation";
export default function SortLabel() {
const params = useSearchParams();
const sort = params.get("sort") ?? "default";
return <p>Sorted by {sort}</p>;
}Add a sort parameter set to "new" to the URL and this renders "Sorted by new". Remove that parameter entirely, and the fallback text appears instead, because the get method returns nothing for a key that is not present in the current query string.
| Hook | Returns |
|---|---|
| usePathname | The route path, no query string or hash |
| useSearchParams | A read-only view of the query string |
There is no method on this object for setting a new value, since it only reads the URL. To change a query parameter, build a new query string from the current one and navigate to it with the useRouter hook.
The Suspense requirement
Calling the search params hook in a component that Next.js tries to prerender makes that component, and everything above it up to the nearest Suspense boundary, render on the client instead. Wrapping the component that reads it in Suspense keeps everything else prerendered normally.
// app/blog/page.tsx
import { Suspense } from "react";
import SortLabel from "@/app/nav/sort-label";
export default function BlogPage() {
return (
<Suspense fallback={<p>Loading sort order…</p>}>
<SortLabel />
</Suspense>
);
}This shows the fallback text briefly on a static build, then the real sort label once the client finishes rendering it. Skipping the boundary still works in local development, which is exactly what makes this mistake easy to miss, but a production build of a static route fails outright without it.
Neither hook works on the server
A Server Component cannot call either hook directly, since both depend on browser APIs that only exist once the component has hydrated on the client. A page reads the same information through its own params and searchParams props instead, which arrive as plain values rather than a hook result, covered in Next.js params vs searchParams: How to Read URL Parameters. For building an active navigation link with segment data instead of a raw path string, see useSelectedLayoutSegment and Building Active Nav Links.
Rune AI
Key Insights
- usePathname returns the path only, without the query string or hash.
- useSearchParams returns a read-only URLSearchParams instance, not a plain object.
- Neither hook works in a Server Component, both require a client directive.
- Wrap a component that calls useSearchParams in Suspense before a production build.
- To update the query string, build a new URLSearchParams value and navigate with the router.
Frequently Asked Questions
Do these hooks work in a Server Component?
Can I set a new value directly on the object useSearchParams returns?
Why does my build fail after adding useSearchParams to a static page?
Conclusion
usePathname returns the current path as a plain string, and useSearchParams returns a read-only view of the query string. Both only work in a Client Component, and a component that calls useSearchParams during static rendering needs a Suspense boundary so the rest of the page can still prerender.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.