next/link and Navigation in Next.js

Next.js provides several ways to navigate: the Link component for declarative navigation, the useRouter hook for programmatic navigation and redirect/permanentRedirect functions for server-side navigation.

Link is the primary way to navigate. It renders an <a> tag but intercepts the click and performs client-side navigation without a full page reload:

import Link from 'next/link'

export default function Navigation() {
  return (
    <nav>
      <Link href="/docs">Knowledge Base</Link>
      <Link href="/problems">Problems</Link>
      <Link href="/roadmap">Roadmap</Link>
    </nav>
  )
}

Prefetching

By default Link prefetches the page when it enters the viewport. This makes transitions instant:

// Prefetch enabled by default
<Link href="/docs/next/ssg">SSG in Next.js</Link>

// Disable prefetch for rarely visited pages
<Link href="/settings" prefetch={false}>Settings</Link>

For static routes prefetch loads the entire RSC Payload. For dynamic routes it loads up to the nearest loading.tsx.

Dynamic href

<Link href={`/docs/${doc.slug}`}>
  {doc.title}
</Link>

// With an object
<Link
  href={{
    pathname: '/problems',
    query: { difficulty: 'hard' }
  }}
>
  Hard problems
</Link>

To highlight the current page use usePathname:

'use client'

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

export function NavLink({
  href,
  children
}: {
  href: string
  children: React.ReactNode
}) {
  const pathname = usePathname()
  const isActive = pathname === href

  return (
    <Link
      href={href}
      className={isActive ? 'text-blue-600 font-bold' : 'text-neutral-600'}
    >
      {children}
    </Link>
  )
}

useRouter

For programmatic navigation in client components:

'use client'

import { useRouter } from 'next/navigation'

export function LoginButton() {
  const router = useRouter()

  const handleLogin = async () => {
    const result = await login()

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

  return <button onClick={handleLogin}>Login</button>
}

useRouter Methods

MethodDescription
router.push(url)Navigate to URL (adds to history)
router.replace(url)Navigate without adding to history
router.back()Go back in history
router.forward()Go forward in history
router.refresh()Refresh current route (re-fetches data from server)
router.prefetch(url)Prefetch a route

Important:

useRouter from next/navigation (App Router) and useRouter from next/router (Pages Router) are different hooks with different APIs. In the App Router there is no router.query, use useSearchParams instead.

Server-Side Navigation

In server components and Server Actions use redirect:

// In a Server Action
'use server'

import { redirect } from 'next/navigation'

export async function createProblem(formData: FormData) {
  const problem = await db.problem.create({ ... })
  redirect(`/problems/${problem.id}`)
}

// In a server component
import { redirect } from 'next/navigation'

export default async function Page() {
  const user = await getUser()
  if (!user) redirect('/auth/login')

  return <Dashboard user={user} />
}

useSearchParams

For working with query parameters:

'use client'

import { useSearchParams } from 'next/navigation'

export function FilterPanel() {
  const searchParams = useSearchParams()
  const difficulty = searchParams.get('difficulty')

  return <p>Filter: {difficulty || 'all'}</p>
}

usePathname

To get the current path:

'use client'

import { usePathname } from 'next/navigation'

export function Breadcrumbs() {
  const pathname = usePathname()
  const segments = pathname.split('/').filter(Boolean)

  return (
    <nav>
      {segments.map((segment, i) => (
        <span key={i}>{segment}</span>
      ))}
    </nav>
  )
}

Useful Resources