Frontend

Caching and revalidation

Every read carries the sync tags it depends on. Use them as cache tags and a publish purges exactly the pages that read the document.

The response envelope carries syncTags: opaque identifiers for the things a result depends on. That is what makes exact invalidation possible instead of a blunt time-based refresh.

ts
const response = await fetch(url, {
  next: { revalidate: 60, tags: cacheTags },
})

What the tags are

  • A dataset-wide tag, always.
  • A per-document tag for each document the read addressed by id.

The asymmetry is forced by the cache rather than chosen: a cache entry's tags must be known before the request is made, and a query's result set is not. So a read by id gets a precise tag, and a filtered query gets the dataset tag.

Purging on write

After any committed write, purge the dataset tag and the tags of the documents the write touched:

ts
revalidateTag(datasetTag(projectId, dataset))
for (const id of writtenIds) revalidateTag(documentTag(projectId, dataset, id))

Choosing a window

Sixty seconds is the default here, and it is a floor rather than a ceiling: with tag purging in place, the window only matters for changes that happen outside the write path — a database edit by hand, say. Raise it if publishing always goes through the API.

Two habits

  1. Split reads. A page that reads its own document by id and its listing separately gets one precise tag and one broad one, instead of one broad one.
  2. Never cache a draft read. Preview must be uncached, or an editor sees the previous minute's work.