Next.js Link and Navigation

Use the Link component, useRouter, and redirects for fast navigation between pages in Next.js.

TL;DR

  1. Navigate between pages with <Link href> instead of a plain anchor tag.
  2. Read the current path in Client Components with usePathname().
  3. Redirect on the server by calling redirect() from next/navigation.
    <Link href>

    Navigate on the client and prefetch the target route in the background.

    import Link from 'next/link';
    
    export default function Nav() {
      return <Link href="/dashboard">Dashboard</Link>;
    }
    Dynamic href

    Build the path with a template string when linking to a dynamic route.

    {posts.map((post) => (
      <Link key={post.id} href={`/blog/${post.slug}`}>
        {post.title}
      </Link>
    ))}
    replace and scroll

    Replace the history entry or keep the scroll position during navigation.

    <Link href="/step-2" replace>
      Next step
    </Link>
    
    <Link href="/feed?tab=new" scroll={false}>
      New
    </Link>
    usePathname()

    Read the current path in a Client Component to style the active link.

    'use client';
    import { usePathname } from 'next/navigation';
    import Link from 'next/link';
    
    export function NavLink({ href }: { href: string }) {
      const active = usePathname() === href;
      const current = active ? 'page' : undefined;
      return (
        <Link href={href} aria-current={current}>
          {href}
        </Link>
      );
    }
    useSearchParams()

    Read query string values in a Client Component wrapped in Suspense.

    'use client';
    import { useSearchParams } from 'next/navigation';
    
    export function Query() {
      const q = useSearchParams().get('q');
      return <p>Searching for {q}</p>;
    }
    Update the Query

    Push a new URL to store filter or search state in the query string.

    const router = useRouter();
    const params = new URLSearchParams();
    params.set('sort', 'price');
    
    router.push(`/products?${params}`);

Programmatic Navigation

    useRouter()

    Navigate from an event handler in a Client Component with router.push.

    'use client';
    import { useRouter } from 'next/navigation';
    
    export function LoginButton() {
      const router = useRouter();
      return (
        <button onClick={() => router.push('/login')}>
          Log in
        </button>
      );
    }
    router methods

    Go back, replace the current entry, or reload fresh data from the server.

    router.back();        // previous page
    router.replace('/');  // no history entry
    router.refresh();     // refetch server data
    redirect()

    Send the user to another route from a Server Component or Server Action.

    import { redirect } from 'next/navigation';
    
    export default async function Page() {
      const user = await getUser();
      if (!user) redirect('/login');
      return <h1>Welcome, {user.name}</h1>;
    }

Prefetching and Feedback

    prefetch

    Turn off automatic prefetching for a link that users rarely click.

    <Link href="/archive" prefetch={false}>
      Archive
    </Link>
    useLinkStatus()

    Show a pending hint inside a link while its navigation is in progress.

    'use client';
    import { useLinkStatus } from 'next/link';
    
    export function Hint() {
      const { pending } = useLinkStatus();
      return pending ? <span>Loading...</span> : null;
    }
    router.prefetch()

    Prefetch a route yourself when you know the user is about to visit it.

    const router = useRouter();
    
    <button onMouseEnter={() => router.prefetch('/cart')}>
      View cart
    </button>

Tips

  1. Use <Link> for anything a user can click, and keep useRouter() for navigation that happens after an event such as a form submit.
  2. Set prefetch={false} on links in very long lists, so the browser does not prefetch hundreds of routes at once.

Warnings

  1. Import useRouter from next/navigation in the App Router, because the next/router version only works in the Pages Router.
  2. Calling redirect() inside a try block fails, because it works by throwing an error that your catch would swallow.

In Practice