revalidatePath()

Purge the cached render of a specific route path so the next visit regenerates it.

Signature
revalidatePath(path: string, type?: 'page' | 'layout'): void

Parameters

ParameterTypeDescription
$pathstringA route file path, either literal ('/product/123') or a route pattern with dynamic segments ('/product/[slug]'). Case-sensitive, max 1024 characters. Do not append /page or /layout, and a trailing slash is never needed regardless of the trailingSlash config.
$typeoptional'page' | 'layout'Which kind of file the path refers to. Required when path contains a dynamic segment; omit it for a literal path. 'layout' also invalidates nested layouts and every page beneath them.

Return value

voidNothing. The path is marked for revalidation and regenerated on the next visit.

Errors

Throws when called in a Client Component or in Proxy, because it only works in server environments.

revalidatePath discards the cached output for one route so the next request re-renders it. Reach for it after a mutation whose blast radius is a page you can name: a post was edited, so /blog/[slug] is wrong, and so is the index that lists it.

Where it behaves differently

From a Server Function it updates the UI right away if the user is viewing the affected path, and it currently also refreshes previously visited pages when they are navigated back to. From a Route Handler it only marks the path; nothing regenerates until somebody visits it. That is deliberate — marking /product/[slug] does not kick off thousands of rebuilds at once.

What trips people up

  • It operates on the route file structure, not the URL in the address bar. With a rewrite from /blog to /news, you must pass /news.
  • Purging a path does not touch cache tags. If /dashboard reads the same tagged data, it keeps serving stale content until that tag is invalidated too — which is why revalidatePath and updateTag are usually called side by side.
  • revalidatePath('/blog/[slug]', 'page') does not reach deeper routes such as /blog/[slug]/[author]; 'layout' does.

Example

Example · typescript
'use server'

import { revalidatePath } from 'next/cache'

export async function publishPost(slug: string) {
  await savePostToDatabase(slug)

  // The post page itself
  revalidatePath(`/blog/${slug}`)
  // ...and the index that lists it
  revalidatePath('/blog')
}

When to use it

  • Refresh a blog post page and its index right after an editor saves changes.
  • Expose a webhook Route Handler your CMS calls with the path it just published.
  • Purge everything on a deploy-adjacent event with revalidatePath('/', 'layout').

Frequently asked questions

Why is my page still showing old data after revalidatePath?

Most likely you called it from a Route Handler, which only marks the path — the fresh render is produced on the next visit, not by the call itself. The other common cause is a rewrite: pass the destination path (the real route file), not the source URL. And if the stale value comes from tagged fetch data shared with another route, you need revalidateTag or updateTag as well.

Do I need the 'page' or 'layout' second argument?

Only when the path contains a dynamic segment. A literal path like /product/1 works on its own; a pattern like /product/[slug] requires the type so Next.js knows which file convention to match. Use 'layout' when you also want every page nested beneath that layout invalidated.

How do I clear the whole Next.js cache at once?

Call revalidatePath('/', 'layout'), which purges the Client Cache and invalidates all cached data for revalidation on the next visit. It is a blunt instrument — prefer tags or specific paths when you know what changed, since a full purge sends every route back to your data sources.

More examples

Webhook Route Handler

Lives at app/api/revalidate/route.ts. A CMS calls /api/revalidate?path=/blog/post-1 and gets back {"revalidated":true,...}. The JSON says the path was accepted, not that HTML was rebuilt — the rebuild happens when the path is next visited.

Example · typescript
import { revalidatePath } from 'next/cache'
import type { NextRequest } from 'next/server'

export async function GET(request: NextRequest) {
  const path = request.nextUrl.searchParams.get('path')

  if (path) {
    revalidatePath(path)
    return Response.json({ revalidated: true, now: Date.now() })
  }

  return Response.json({
    revalidated: false,
    now: Date.now(),
    message: 'Missing path to revalidate',
  })
}

Invalidating every page under a layout

Shows when the second argument is actually needed. Only a path containing a dynamic segment requires 'page' or 'layout' — the official docs say to omit the type for a literal path, so '/(marketing)/about' would normally be revalidated with revalidatePath('/(marketing)/about') alone. A route group directory is still written out either way, because the path mirrors the app directory, not the browser URL.

Example · typescript
'use server'

import { revalidatePath } from 'next/cache'

export async function renameStore() {
  await updateStoreSettings()

  // Every /shop/[category] page, plus anything nested beneath them
  revalidatePath('/shop/[category]', 'layout')

  // Route groups are part of the file path too
  revalidatePath('/(marketing)/about', 'page')
}

Pairing a path purge with a tag update

Demonstrates why one call is rarely enough: revalidatePath fixes /blog, while any other route that fetched with next.tags: ['posts'] keeps serving its own cached copy until the tag is invalidated.

Example · typescript
'use server'

import { revalidatePath, updateTag } from 'next/cache'

export async function updatePost(id: string) {
  await updatePostInDatabase(id)

  revalidatePath('/blog') // this page
  updateTag('posts') // every page reading the 'posts' tag
}

Discussion

  • Be the first to comment on this lesson.