refresh()

Refetches the current route's RSC Payload from inside a Server Action without invalidating caches.

Signature
refresh(): void

Return value

voidDoes not return a value. It does not throw a control-flow error either, so an action can call it and still return a value to the caller.

Errors

Throws when called anywhere other than a Server Action — Route Handlers, Client Components, and any other context are rejected.

refresh() from next/cache tells Next.js to refetch the current route's RSC Payload as part of the Server Action's own response. The re-rendered tree arrives in the same HTTP request as the action's return value, so the UI updates in a single roundtrip with no follow-up fetch from your code.

What separates it from the revalidation APIs is that it invalidates nothing. updateTag and revalidatePath expire cache entries; refresh() leaves the cache exactly as it is and simply re-renders. Reach for it when the view depends on state that lives outside the Next.js cache — a session cookie, a row read uncached on every request, an external system the action just poked.

Server Actions only

It throws in a Route Handler, in a Client Component, and anywhere else. Client-side, the equivalent is router.refresh() from useRouter.

Unlike redirect(), it does not throw a control-flow error, so code after it still runs and the action can return a value normally. Place it after the mutation has completed.

Example

Example · typescript
'use server'

import { refresh } from 'next/cache'
import { db } from '@/lib/db'

export async function createPost(formData: FormData) {
  const title = formData.get('title')
  const content = formData.get('content')

  await db.post.create({
    data: { title, content },
  })

  refresh()
}

When to use it

  • Re-render the current page after an action changes state held outside the cache
  • Reflect a freshly written row that is read uncached on every request
  • Update the UI after an action calls an external service that owns the data
  • Refresh the view after an action mutates a session or user preference

Frequently asked questions

What is the difference between refresh and revalidatePath in Next.js?

refresh() re-renders the current route without invalidating any cached data, while revalidatePath() expires the cache entries for a path so the next read fetches fresh values. Use refresh() when the changed state lives outside the cache.

Why does refresh() throw an error in my Route Handler?

Because it can only be called from a Server Action. Route Handlers, Client Components, and every other context are rejected — there is no current route render for them to attach the refreshed payload to. Use revalidatePath or revalidateTag there instead.

How do I refresh the router from a Client Component?

Use router.refresh() from the useRouter hook in next/navigation. The refresh() export from next/cache is the server-side counterpart and works only inside a Server Action, where it rides along in the action's own response.

More examples

Refreshing and still returning a value

refresh() does not throw, so the return statement below it still runs and useActionState receives { error: null } on the client. The same response also carries the re-rendered RSC Payload. Note that cookies() is async in Next.js 15+ and must be awaited.

Example · typescript
// app/settings/actions.ts
'use server'

import { refresh } from 'next/cache'
import { cookies } from 'next/headers'

export async function setTheme(_prev: unknown, formData: FormData) {
  const theme = String(formData.get('theme'))

  if (theme !== 'light' && theme !== 'dark') {
    return { error: 'Unknown theme' }
  }

  const cookieStore = await cookies()
  cookieStore.set('theme', theme)

  refresh()

  return { error: null }
}

The error when it is called outside a Server Action

A Route Handler has no client router to refresh — there is no current route being re-rendered for a caller — so the call fails rather than becoming a no-op. Route Handlers should use revalidatePath or revalidateTag to invalidate cached data instead.

Example · typescript
// app/api/posts/route.ts
import { refresh } from 'next/cache'

export async function POST() {
  // Throws: refresh() can only be called from a Server Action.
  refresh()
}

Choosing refresh over revalidatePath

Both actions re-render the current route in the same response. The difference is what happens to cached data: syncNow leaves every cache entry intact and only re-renders, while renameAccount expires the /dashboard entry so the re-render reads fresh values. Using refresh() where revalidatePath() was needed produces a re-render that still shows the old cached data.

Example · typescript
// app/dashboard/actions.ts
'use server'

import { refresh, revalidatePath } from 'next/cache'
import { syncBillingAccount } from '@/lib/billing'
import { db } from '@/lib/db'

export async function syncNow(accountId: string) {
  // The remote provider owns this state; nothing of ours is cached.
  await syncBillingAccount(accountId)
  refresh()
}

export async function renameAccount(accountId: string, name: string) {
  // This page reads a cached query, so the cache entry must expire.
  await db.account.update({ where: { id: accountId }, data: { name } })
  revalidatePath('/dashboard')
}

Discussion

  • Be the first to comment on this lesson.