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 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 |
"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.
A navigation bar needs to know which item is current:
"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>
);
}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:
{ scroll: false } as the second argument to router.push to keep the scroll position while a filter changes.useSearchParams() in a statically rendered route must sit inside a <Suspense> boundary, otherwise the build reports a missing boundary error.searchParams prop instead.Inside Server Components, Route Handlers and Server Actions, use the redirect function instead of a hook:
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.
useRouter from next/router in the App Router; it throws at runtime.URLSearchParams.useParams() returns strings, so numeric ids must be parsed.Which method re-fetches Server Component data for the current route without losing client state?
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.URLSearchParams and calling router.push.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.