Payload Plugins
Pluginspayload-revalidate

payload-revalidate

Edit one document and only the pages that show it go stale — precise Next.js cache revalidation for Payload, no full-cache purges.

For AI / LLMs: View Markdown

@pro-laico/payload-revalidate makes Next.js cache revalidation precise for Payload. Edit one document and only the pages that show it go stale. No more purging the whole cache on every content change.

Before: edit an image's alt text, purge the whole cache, every page rebuilds. After: only that image's own cache entry refreshes — everywhere the image appears — while every surrounding page, list, and card entry survives.

pnpm add @pro-laico/payload-revalidate

What's included

  • Surgical invalidation — edit one document and only the entries that render it go stale; every surrounding page, list, and card survives.
  • Field-driven busts — each bust is computed from which fields changed, so a title edit refreshes one card, not the whole archive.
  • Cache helpers that fetch and tag together — write a getter as 'use cache' plus one finder call; the finder runs the query and tags the entry.
  • Zero-config sibling defaults — icons, images, mux, fonts, and seed integrate automatically when the plugin is registered last.
  • A live dependency map/dev/revalidate shows exactly what revalidates when (with payload-dev-tools).

Requirements

Next.js 16 with Cache Components enabled, on the App Router.

next.config.ts
const nextConfig = { cacheComponents: true }

This plugin asks you to adopt a getter pattern: each cached read is a 'use cache' function plus one finder call, and the cache helpers are seeded once with a live Payload handle. The Quickstart walks the whole path.

Quickstart

Install

pnpm add @pro-laico/payload-revalidate

Enable Cache Components

next.config.ts
const nextConfig = { cacheComponents: true }

Register the plugin last

Add it last in plugins: [], so it can hook collections other plugins contribute.

payload.config.ts
import { buildConfig } from 'payload'
import { revalidatePlugin } from '@pro-laico/payload-revalidate'

export default buildConfig({
  // ...
  plugins: [
    // ...your other plugins
    revalidatePlugin(), // add it LAST
  ],
})

Zero-config works: every collection and global gets hooks and doc tags automatically. Declaring list scopes is the one thing worth configuring — see Examples and Reference.

Seed the cache helpers

Seed the helpers once with your app's live Payload session. Export the handle so getters and manual busters share it. getPayload returns a promise — hand it in as-is; createCacheHelpers accepts a Payload or Promise<Payload> and awaits it internally, so there's no top-level await.

src/lib/getters.ts
import config from '@payload-config'
import { getPayload } from 'payload'
import { createCacheHelpers } from '@pro-laico/payload-revalidate/cache'

export const db = getPayload({ config })
const { findDoc, findDocByID, findIds, findGlobal } = createCacheHelpers(db)

Two entry points: the package root exports revalidatePlugin and tagsFor; the /cache subpath exports createCacheHelpers. The finders and the manual busters come from what createCacheHelpers(db) returns. See Exports.

Write a getter

Each getter is 'use cache' plus one finder call. The finder runs the query and tags the entry.

src/lib/getters.ts
/** THE atomic unit: one doc, one entry, shared by every usage site. */
export async function getPost(id: string | number) {
  'use cache'
  return findDocByID('posts', id)
}

/** The id-list: membership/order only. Content edits never touch this entry. */
export async function getRecentPostIds(page = 1) {
  'use cache'
  return (await findIds('posts', { page, limit: 12, sort: '-publishedAt', list: 'recent' })).ids
}

A named list scope ('recent' here) must be declared in the plugin config, listing the sort/filter fields it depends on, or reorders won't bust it. See Examples and Reference.

Render ids, self-fetch each card

The list renders ids; each card fetches its own doc through the id-keyed getter — one cache entry per card.

app/(frontend)/PostList.tsx
async function PostList() {
  const ids = await getRecentPostIds()
  return ids.map((id) => <PostCard key={id} id={id} />)
}

async function PostCard({ id }: { id: string | number }) {
  const post = await getPost(id)
  if (!post) return null
  return <article>…</article>
}

Explore

Plugin options

Zero-config by default — every option is optional. See Reference for every option, its default, and the full export map.

On this page