Navigation Hooks: useRouter, usePathname and useSearchParams

Beginner
11 min

Navigation Hooks: useRouter, usePathname and useSearchParams

Link covers navigation a user triggers by clicking, but plenty of transitions happen in code: after a form submits, when a filter changes, or when a component needs to know which route is active. The next/navigation module exposes a small set of hooks and functions for this. After this lesson you will be able to navigate programmatically, read the current path and query string, and update URL state without a page reload.

The next/navigation Toolkit

The hooks work only in Client Components, so the file must start with "use client". The redirect family works on the server instead.

| API | Runs in | Purpose | |---|---|---| | useRouter() | Client | push, replace, back, forward, refresh, prefetch | | usePathname() | Client | Current path such as /blog/hello | | useSearchParams() | Client | Read-only URLSearchParams of the query string | | useParams() | Client | Dynamic segment values such as { slug: "hello" } | | redirect(), permanentRedirect() | Server | Send the user elsewhere during render or in an action |

useRouter for Programmatic Navigation

tsx
"use client"; import { useRouter } from "next/navigation"; export default function LogoutButton() { const router = useRouter(); async function logout() { await fetch("/api/logout", { method: "POST" }); router.push("/login"); // replace() would overwrite the history entry instead } return <button onClick={logout}>Log out</button>; }

router.refresh() re-runs the Server Components for the current route and merges the result into the page without losing client state such as scroll position or form input. It is the standard way to show fresh server data after a mutation made with fetch.

usePathname for Active Links

A navigation bar needs to know which item is current:

tsx
"use client"; import Link from "next/link"; import { usePathname } from "next/navigation"; export default function Nav() { const pathname = usePathname(); const active = (href: string) => (pathname.startsWith(href) ? "page" : undefined); return ( <nav> <Link href="/docs" aria-current={active("/docs")}>Docs</Link> <Link href="/blog" aria-current={active("/blog")}>Blog</Link> </nav> ); }

useSearchParams and URL State

Query strings are the best place for shareable UI state such as filters, sorting and pagination. useSearchParams() returns a read-only object, so to change it you build a new URLSearchParams and push a new URL, as in the sample at the top of this lesson. Three details matter:

  • Pass { scroll: false } as the second argument to router.push to keep the scroll position while a filter changes.
  • A component that calls useSearchParams() in a statically rendered route must sit inside a <Suspense> boundary, otherwise the build reports a missing boundary error.
  • In Server Components read the query string from the page's searchParams prop instead.

Redirecting on the Server

Inside Server Components, Route Handlers and Server Actions, use the redirect function instead of a hook:

tsx
import { redirect } from "next/navigation"; import { getSession } from "@/lib/session"; export default async function AccountPage() { const session = await getSession(); if (!session) redirect("/login?next=/account"); return <h1>Welcome back, {session.user.name}</h1>; }

redirect() sends a 307 status and permanentRedirect() a 308. Both work by throwing, so call them outside try/catch blocks.

Common mistakes

  • Importing useRouter from next/router in the App Router; it throws at runtime.
  • Building query strings by concatenation without encoding; always use URLSearchParams.
  • Forgetting that useParams() returns strings, so numeric ids must be parsed.
Quick Quiz
Question 1 of 3

Which method re-fetches Server Component data for the current route without losing client state?

Key Takeaways

  • Import navigation hooks from next/navigation, never from next/router.
  • useRouter() provides push, replace, back, refresh and prefetch for navigation in code.
  • usePathname() and useParams() expose the current route; useSearchParams() the query string.
  • Update URL state by building a new URLSearchParams and calling router.push.
  • On the server, redirect() and permanentRedirect() replace the hooks and stay outside try/catch.

Next lesson: Server Components vs Client Components — understand the boundary that decides where each component runs.

Navigation Hooks: useRouter, usePathname and useSearchParams - Next.js | CodeYourCraft | CodeYourCraft