Haliviq
Next.js

revalidatePath doesn't bust the client-side router cache

The server data was fresh, but users still saw stale content for up to 30 seconds because the browser keeps its own router cache that revalidatePath does not touch. When freshness matters, such as after a form submit, trigger a router refresh on the client as well, and test the flow by clicking around rather than only reloading.

What happened

After a user submitted a form, the server data was already updated, yet going back to the list page still showed the old content for up to 30 seconds. A hard reload showed fresh data, which made the problem hard to reproduce and easy to dismiss.

Why it happens

revalidatePath purges the server-side caches for that path: the Data Cache and the Full Route Cache. The browser also holds a client-side Router Cache that stores the RSC payloads of pages already visited. It lives in the browser's memory, so the server cannot simply clear it. In Next.js 14, dynamic pages stay in it for 30 seconds.

One condition matters: when revalidatePath is called inside a Server Action, Next.js also invalidates the client Router Cache for you. When the mutation goes through a Route Handler or an external API, that does not happen, and visited pages can stay stale. In Next.js 15, pages are not reused from the Router Cache by default (staleTimes of 0), so this shows up less, though back and forward navigation may still show a stored view.

What we do now

When the mutation does not go through a Server Action, call router.refresh() from useRouter on the client after the request succeeds. It refreshes the current route and fetches fresh data. Alternatively, move the mutation into a Server Action and call revalidatePath there.

Test the flow by submitting, following a link to another page, and coming back to the list. A reload hides this problem.

Refresh the router on the client after a non-Server-Action mutationtsx
'use client'
import { useRouter } from 'next/navigation'

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

  async function onSubmit(formData: FormData) {
    const res = await fetch('/api/posts', { method: 'POST', body: formData })
    if (res.ok) router.refresh()
  }

  return <form action={onSubmit}>{/* fields */}</form>
}

Quick checklist

  1. 1Check whether the mutation goes through a Server Action or a Route Handler.
  2. 2For a Route Handler, call router.refresh() after the request succeeds.
  3. 3For a Server Action, call revalidatePath inside it.
  4. 4Test by clicking between pages, not only by reloading.

Rule of thumb

After a mutation that is not a Server Action, do not rely on revalidatePath alone: refresh the router on the client too, and test by clicking.

We'd love to hear what you're building.

Talk to us