Last updated: 2026-10-08

Navigation and Linking

Navigation is the mechanism by which users move through an application. In a traditional website, every link triggers a full page reload — the browser makes a fresh request to the server, which returns a complete HTML document, which the browser renders from scratch. This works, but it is slow, it loses client state, and it produces the jarring visual reset of a blank screen between pages.

Next.js takes a different approach. Navigation within a Next.js application happens on the client, without full page reloads, while still benefiting from server-rendered pages on the initial load. The result is an experience that feels as fast and seamless as a single-page application, without sacrificing the performance and SEO advantages of server rendering.

Understanding how Next.js navigation works — and the tools it provides to control it — is what makes building coherent, well-behaved applications possible.


How Navigation Works in the App Router

When a user first visits a Next.js application, the server renders the requested page and sends fully-formed HTML to the browser. This initial load is server-rendered, which is why content appears immediately and search engines can read it.

When the user then clicks a link to navigate to another page within the same application, the behaviour changes. The browser does not reload. Instead, Next.js:

  1. Intercepts the navigation
  2. Fetches only the new page segment from the server
  3. Updates the URL in the browser's address bar
  4. Renders the new page content on the client

Layouts that are shared between the current route and the destination are not re-fetched or re-rendered — they stay exactly as they are. Only the parts of the UI that actually changed are updated. This is called partial rendering, and it is one of the reasons navigation in Next.js feels instant even for complex pages.


The <Link> Component

The primary tool for navigation in Next.js is the <Link> component, imported from next/link. It renders as an <a> tag in the browser but intercepts clicks to perform client-side navigation instead of a full reload.

import Link from 'next/link';

export default function Navigation() {
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/about">About</Link>
      <Link href="/blog">Blog</Link>
    </nav>
  );
}

This is the correct way to link between pages in a Next.js application. Using a plain HTML <a> tag works — it navigates to the correct page — but it triggers a full browser reload, losing all the performance benefits of client-side navigation and resetting any client state that should have persisted.

Linking to Dynamic Routes

For routes with dynamic segments, the href is constructed with the actual value substituted in for the segment placeholder:

import Link from 'next/link';

export default function PostList({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  );
}

Styling the Active Link

A common UI requirement is styling a navigation link differently when its destination is the current page — highlighting the active item in a nav bar, for example. Next.js provides the usePathname hook, imported from next/navigation, to read the current URL path and compare it against a link's destination.

'use client';

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

const links = [
  { href: '/', label: 'Home' },
  { href: '/about', label: 'About' },
  { href: '/blog', label: 'Blog' },
];

export default function Navigation() {
  const pathname = usePathname();

  return (
    <nav>
      {links.map(({ href, label }) => (
        <Link
          key={href}
          href={href}
          style={{ fontWeight: pathname === href ? 'bold' : 'normal' }}
        >
          {label}
        </Link>
      ))}
    </nav>
  );
}

Because usePathname is a hook, it requires the component to be a Client Component — hence the 'use client' directive.

Prefetching

By default, Next.js prefetches the destination of every <Link> component that is visible in the viewport. Prefetching means the destination page's code and data are fetched in the background before the user clicks, so that when they do click, the navigation feels instantaneous — the content is already loaded.

This behaviour is automatic and requires no configuration. It can be disabled on a per-link basis when prefetching is not desirable — for example, on links behind authentication that should not be preloaded for unauthenticated users:

<Link href="/dashboard" prefetch={false}>
  Dashboard
</Link>

Prefetching only applies in production. In development, it is disabled to keep the development server responsive.


Programmatic Navigation — useRouter

Not all navigation is triggered by a user clicking a link. Forms that redirect after submission, buttons that trigger navigation as a side effect, authentication flows that redirect after login — these all require navigation to happen programmatically, in code, rather than through a link element.

The useRouter hook, imported from next/navigation, provides this capability. It is a Client Component hook — it requires 'use client'.

'use client';

import { useRouter } from 'next/navigation';

export default function LoginForm() {
  const router = useRouter();

  async function handleSubmit(event) {
    event.preventDefault();
    const success = await loginUser(/* form data */);

    if (success) {
      router.push('/dashboard');
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      {/* form fields */}
      <button type="submit">Log In</button>
    </form>
  );
}

The router object provides several navigation methods:

MethodBehaviour
router.push(href)Navigate to href, adding a new entry to the browser history
router.replace(href)Navigate to href, replacing the current history entry
router.back()Navigate to the previous page in the browser history
router.forward()Navigate to the next page in the browser history
router.refresh()Refresh the current page, re-fetching server data
router.prefetch(href)Manually prefetch a route

router.push() is the standard navigation method — it behaves like clicking a link and adds an entry to the browser history, so the user can press the back button to return. router.replace() is used when the current entry should be discarded — after a login redirect, for example, so that pressing back does not return the user to the login page they have already passed through.


Reading the Current Route

Several hooks are available for reading information about the current route, all imported from next/navigation. All of them are Client Component hooks.

usePathname

Returns the current URL path as a string, without the query string.

'use client';
import { usePathname } from 'next/navigation';

export default function Breadcrumb() {
  const pathname = usePathname();
  // '/blog/my-first-post'
  return <span>{pathname}</span>;
}

useSearchParams

Returns the current URL's query string as a URLSearchParams object, allowing individual parameters to be read.

'use client';
import { useSearchParams } from 'next/navigation';

export default function SearchResults() {
  const searchParams = useSearchParams();
  const query = searchParams.get('q');
  // URL: /search?q=nodejs → query = 'nodejs'

  return <p>Results for: {query}</p>;
}

useParams

Returns the dynamic route parameters for the current route as an object.

'use client';
import { useParams } from 'next/navigation';

export default function PostActions() {
  const params = useParams();
  // URL: /blog/my-first-post → params = { slug: 'my-first-post' }

  return <button>Share: {params.slug}</button>;
}

Redirects

Navigation is not always initiated by the user. Server-side redirects — sending the user to a different URL automatically, before the page renders — are handled with the redirect function from next/navigation.

import { redirect } from 'next/navigation';

export default async function ProtectedPage() {
  const session = await getSession();

  if (!session) {
    redirect('/login');
  }

  return <h1>Protected Content</h1>;
}

redirect() can be called in Server Components, Route Handlers, and Server Actions. It cannot be called in Client Components — for client-side redirects, router.push() or router.replace() is the correct tool.

For redirects that apply to an entire section of the application regardless of which page within it is visited — redirecting all /old-blog/* routes to /blog/*, for example — Next.js supports redirect definitions directly in next.config.ts:

// next.config.ts
const nextConfig = {
  async redirects() {
    return [
      {
        source: '/old-blog/:slug',
        destination: '/blog/:slug',
        permanent: true,
      },
    ];
  },
};

export default nextConfig;

A permanent: true redirect issues a 308 status code, telling browsers and search engines that the old URL has moved permanently. A permanent: false redirect issues a 307, indicating a temporary move.


Scroll Behaviour

By default, Next.js scrolls to the top of the page when navigating to a new route. This is the expected behaviour for most page-to-page navigation.

When linking to a specific section of a page using a hash, Next.js handles this correctly as well:

<Link href="/blog#section-two">Jump to Section Two</Link>

To navigate to a new route without scrolling to the top — useful when a layout component manages scroll position independently — the scroll behaviour can be disabled:

<Link href="/blog" scroll={false}>
  Blog
</Link>

Programmatically:

router.push('/blog', { scroll: false });

Summary

ToolImportUse Case
<Link>next/linkDeclarative navigation between pages
useRouternext/navigationProgrammatic navigation in Client Components
redirect()next/navigationServer-side redirect in Server Components and Route Handlers
usePathnamenext/navigationRead the current URL path
useSearchParamsnext/navigationRead the current URL query string
useParamsnext/navigationRead dynamic route parameters
Prefetching—Automatic for visible <Link> elements in production
Config redirectsnext.config.tsApplication-wide URL redirects