useRouter()

Navigate, refresh, and prefetch routes programmatically from a Client Component.

Signature
import { useRouter } from 'next/navigation'

const router = useRouter()

router.push(href: string, { scroll: boolean, transitionTypes: string[] })
router.replace(href: string, { scroll: boolean, transitionTypes: string[] })
router.refresh()
router.prefetch(href: string, options?: { onInvalidate?: () => void })
router.back()
router.forward()
  • Added in Next.js v13.0.0

Return value

AppRouterInstanceThe App Router instance, exposing push, replace, refresh, prefetch, back, forward and the opaque bfcacheId string for the current route segment.

Errors

Importing useRouter from next/router in the App Router throws "NextRouter was not mounted", and calling the hook outside a Client Component fails because there is no router context.

Changelog

  • PHP v15.4.0Optional onInvalidate callback for router.prefetch introduced. It fires at most once per prefetch request, when the prefetched data becomes stale.
  • PHP v13.0.0useRouter from next/navigation introduced. The pathname string, the query object and router.events from the next/router hook are all gone; use usePathname and useSearchParams instead.

useRouter hands a Client Component the App Router instance so it can move the user without rendering a link. Reach for it when the navigation is a consequence of something else: a login request succeeded, a modal closed, a wizard step finished. When the user is clicking a thing that goes somewhere, use <Link> instead — it prefetches on its own and stays a real anchor for middle-click and screen readers.

Methods on the returned object

MethodWhat it does
push(href, options?)Client-side navigation that adds a browser history entry.
replace(href, options?)The same navigation without adding a history entry.
refresh()Re-requests the current route and re-renders Server Components, merging the new payload while keeping useState and scroll position.
prefetch(href, options?)Warms a route for a faster transition.
back() / forward()Move through the history stack.

What trips people up

  • refresh() clears the client cache for the route but does not invalidate server-side caches. If a fetch response is cached you get the same HTML back; call revalidatePath or revalidateTag first.
  • There is no router.events. Compose usePathname and useSearchParams in an effect.
  • Never pass an unsanitized URL to push or replace — a javascript: URL executes in your page.
  • In a Server Component, use redirect(), not this hook.

Example

Example · typescript
'use client'

import { useState } from 'react'
import { useRouter } from 'next/navigation'

export default function LoginForm() {
  const router = useRouter()
  const [error, setError] = useState<string | null>(null)

  async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()

    const res = await fetch('/api/login', {
      method: 'POST',
      body: new FormData(event.currentTarget),
    })

    if (!res.ok) {
      setError('Invalid credentials')
      return
    }

    // Re-render Server Components so the session cookie is picked up,
    // then replace the login entry so Back does not return to it.
    router.refresh()
    router.replace('/dashboard')
  }

  return (
    <form onSubmit={onSubmit}>
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      {error ? <p role="alert">{error}</p> : null}
      <button type="submit">Sign in</button>
    </form>
  )
}

When to use it

  • Send the user to /dashboard after a client-side sign-in request returns 200.
  • Call router.refresh() after a mutation so the Server Component list re-renders with fresh data.
  • Wire a modal's Cancel button to router.back() so the URL and history stay in sync.
  • Prefetch a checkout route on hover when the Link has prefetch={false}.

Frequently asked questions

Why does useRouter throw "NextRouter was not mounted"?

You imported it from next/router, the Pages Router hook, inside the app directory. Change the import to next/navigation — the two hooks have different APIs, and the App Router one has no pathname, query, or events.

How do I redirect from a Server Component in the App Router?

Use redirect() or permanentRedirect() from next/navigation instead of useRouter — hooks only run in Client Components, and redirect() works in Server Components, Route Handlers, and Server Actions.

Why doesn't router.refresh() show my updated data?

refresh() clears only the client-side cache for the current route; a cached fetch on the server returns the same response. Call revalidatePath or revalidateTag in a Server Action to invalidate the server cache first.

More examples

Keeping the scroll position on navigation

By default Next.js scrolls to the top of the page on every navigation. Passing { scroll: false } to push or replace keeps the viewport where it is, which is what you want for pagination, filters, and tab switches that only change part of the page.

Example · typescript
'use client'

import { useRouter } from 'next/navigation'

export default function Pagination({ page }: { page: number }) {
  const router = useRouter()

  return (
    <button
      type="button"
      onClick={() => router.push(`?page=${page + 1}`, { scroll: false })}
    >
      Next page
    </button>
  )
}

Replacing router.events with a navigation effect

The App Router removed router.events, so route-change listeners are rebuilt from usePathname and useSearchParams. The Suspense boundary is required: useSearchParams forces client-side rendering up to the nearest boundary during prerendering, and without one a production build of a static route fails.

Example · typescript
// app/components/navigation-events.tsx
'use client'

import { useEffect } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'

export function NavigationEvents() {
  const pathname = usePathname()
  const searchParams = useSearchParams()

  useEffect(() => {
    const url = `${pathname}?${searchParams}`
    // Report the page view to your analytics provider
    console.log(url)
  }, [pathname, searchParams])

  return null
}

// app/layout.tsx
import { Suspense } from 'react'
import { NavigationEvents } from './components/navigation-events'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Suspense fallback={null}>
          <NavigationEvents />
        </Suspense>
      </body>
    </html>
  )
}

Prefetching manually and reacting to stale data

router.prefetch fills the client cache before the user commits to the navigation. The onInvalidate callback (Next.js 15.4 and later) fires at most once per prefetch, signalling that the cached payload is stale and a fresh prefetch is worth issuing.

Example · typescript
'use client'

import { useCallback } from 'react'
import { useRouter } from 'next/navigation'

export default function CheckoutButton() {
  const router = useRouter()

  const warm = useCallback(() => {
    router.prefetch('/checkout', {
      onInvalidate: () => {
        // Fired once when the prefetched payload goes stale.
        router.prefetch('/checkout')
      },
    })
  }, [router])

  return (
    <button type="button" onMouseEnter={warm} onClick={() => router.push('/checkout')}>
      Checkout
    </button>
  )
}

Discussion

  • Be the first to comment on this lesson.