NextjsuseRouter & Programmatic Navigation

useRouter & Programmatic Navigation

Sometimes navigation needs to happen as a result of code running, not as a direct response to a user clicking a link — after a form successfully submits, after an authentication check completes, or after a timer expires. That's what the useRouter hook is for.

Warning
Import useRouter from next/navigation, not next/router. next/router was the Pages Router API and either doesn't exist or behaves incorrectly in the App Router. This is one of the most common App Router migration mistakes.
Getting Access to the Router

useRouter is a Client Component hook — it relies on browser APIs and React state under the hood, so the component calling it must include the 'use client' directive.

TSX
'use client'
import { useRouter } from 'next/navigation'

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

  return <button onClick={() => router.back()}>Go back</button>
}
The Router Methods

Method

What It Does

router.push(href)

Navigates to a new route, adding an entry to the browser history.

router.replace(href)

Navigates to a new route without adding a history entry — the back button skips it.

router.back()

Navigates to the previous entry in the history stack, like the browser back button.

router.forward()

Navigates forward in the history stack.

router.refresh()

Re-fetches the current route from the server, refreshing Server Component data without losing client state.

Worked Example: Redirect After Submit

A very common pattern is sending a user to a confirmation or detail page right after a form submission succeeds.

TSX
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'

export function CreatePostForm() {
  const router = useRouter()
  const [title, setTitle] = useState('')
  const [isSubmitting, setIsSubmitting] = useState(false)

  async function handleSubmit(event: React.FormEvent) {
    event.preventDefault()
    setIsSubmitting(true)

    const response = await fetch('/api/posts', {
      method: 'POST',
      body: JSON.stringify({ title }),
    })

    const { id } = await response.json()
    setIsSubmitting(false)

    // Navigate to the new post's page once creation succeeds.
    router.push(`/blog/${id}`)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Post title" />
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Saving...' : 'Create Post'}
      </button>
    </form>
  )
}
push vs replace

Use replace instead of push for flows where you don't want the user to be able to navigate back to the previous screen — a classic example is redirecting away from a login page after a successful sign-in, so the back button doesn't return the user to the login form.

TSX
'use client'
import { useRouter } from 'next/navigation'

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

  async function handleLogin() {
    // ...authenticate...
    router.replace('/dashboard')
  }

  return <button onClick={handleLogin}>Log in</button>
}
When to Reach for router.refresh()

refresh() is handy when a client-side action changes server data (say, through an API route) and you want the currently rendered Server Components to reflect that change, without a full navigation and without losing any client-side state like scroll position or open dropdowns.

  • push and replace both accept the same href formats as Link.

  • useRouter only works inside Client Components — it cannot be called in a Server Component.

  • For links a user clicks directly, prefer the Link component over router.push in an onClick.

  • Server Actions often make router.push unnecessary — the redirect() function (covered later) can handle navigation directly from server code.

Note
There is also a lower-level useRouter export from next/router preserved only for Pages Router compatibility layers — in an App Router project you should never need it. Always import from next/navigation.
Tip
If you find yourself calling router.push purely to respond to a click on something that looks like a link, consider whether a plain Link component would be simpler and get automatic prefetching for free.

useRouter is the escape hatch for navigation that needs to happen inside your own logic rather than declaratively in JSX. Combined with the Link component, it covers essentially every navigation scenario you'll run into in an App Router application.