Integrating cache tags in your project
Whatever your stack, an integration comes down to two things: marking each cached artifact with the tags that the Content Delivery API returned, and purging those tags when the invalidation webhook fires. How you do them depends on what sits between your code and your visitors.
This page starts with the general case — any server, any CDN — because it is both the simplest to reason about and a good way to see the mechanism concretely. The second half covers popular frameworks and hosting platforms, where the picture is less uniform.
Any server, any CDN
If your application can set custom HTTP headers on a per-page basis, then regardless of language or framework you can use cache tags by placing a CDN that supports tag-based invalidation on top of it.
What is tag-based cache invalidation?
Tag-based cache invalidation is a method where keywords (tags) can be assigned to cached pages. This technique is provided by all the major content delivery services such as Netlify, Fastly, Bunny and Cloudflare. In a nutshell:
Assign Tags: When your application delivers a page, it can specify a series of tags in a specific response header (the header's name depends on the CDN). These tags serve as labels, that represent the content within that page.
Caching: The response is stored in the CDN cache with its primary cache key — the URL — plus the associated tags.
Purging: If any content linked to a particular tag is updated, instead of searching through all cached pages, the CDN can quickly identify and remove all items associated with that specific tag.
It's important to know that different services use different names for the same underlying concept technology. For example, Fastly refers to cache tags as "Surrogate Keys". The header with which your application can declare the tags to the CDN also varies depending on the service. With Netlify and Cloudflare, the name is Cache-Tag, while Bunny refers to it as CDN-Tag. Netlify also accepts Netlify-Cache-Tag, which is preferable there because it is removed from the response before it reaches the browser. What we in this documentation call "cache invalidation," other services refer to as "cache purge".
Make sure to refer to the specific documentation of your CDN to know the details, format, and any potential limitations.
The whole integration, end to end
The integration is small enough to show in full. The example below uses Hono as the server and Fastly as the CDN, but nothing in it is specific to either: the same two response headers and the same purge call apply to Express, Fastify, Rails, Laravel, or anything else that can set a header.
Tagging a response. Ask the Content Delivery API for cache tags, then forward them to the CDN:
import { Hono } from 'hono';import { rawExecuteQuery } from '@datocms/cda-client';
const app = new Hono();
app.get('/posts/:slug', async (c) => { const [data, response] = await rawExecuteQuery(POST_QUERY, { token: process.env.DATOCMS_CDA_TOKEN, returnCacheTags: true, variables: { slug: c.req.param('slug') }, });
const cacheTags = response.headers.get('x-cache-tags');
if (cacheTags) { c.header('Surrogate-Key', cacheTags); c.header('Surrogate-Control', 'max-age=31536000'); }
return c.html(renderPost(data));});Two things are worth pointing out:
X-Cache-Tagsis space-separated, and so is Fastly'sSurrogate-Key: the value can be forwarded verbatim. CDNs that expect a comma-separatedCache-TagneedcacheTags.split(' ').join(',')instead.Surrogate-Controltells the CDN how long to keep the response, and Fastly strips it before the response reaches the browser. A one-year lifetime on the CDN is therefore safe: cache tags — not time — are what expire the entry. Use a regularCache-Controlheader if you also want visitors' browsers to cache the page, with a much shorter lifetime.
Purging on content change. The webhook endpoint receives the tags and hands them to the CDN's purge API:
app.post('/api/invalidate-cache', async (c) => { if (c.req.header('authorization') !== `Bearer ${process.env.WEBHOOK_TOKEN}`) { return c.json({ success: false }, 401); }
const { entity } = await c.req.json(); const { tags } = entity.attributes;
const response = await fetch( `https://api.fastly.com/service/${process.env.FASTLY_SERVICE_ID}/purge`, { method: 'POST', headers: { 'fastly-key': process.env.FASTLY_KEY, 'content-type': 'application/json', }, body: JSON.stringify({ surrogate_keys: tags }), }, );
return c.json({ success: response.ok }, response.ok ? 200 : 502);});That's the entire integration: two response headers on the way out, one POST on the way in.
Popular frameworks and hosting platforms
Some frameworks offer their own caching layer, with helpers that work across hosting providers instead of raw HTTP headers. When one is available it can be the more idiomatic path — but the frameworks differ in how well they carry a set of cache tags from end to end, and that difference is worth knowing before you commit to a stack.
The number of possible framework and hosting combinations is far larger than any documentation could cover. What we maintain is a selection of guides, chosen among the setups that are most frequently asked about and the ones we are most comfortable recommending.
What makes a stack a good fit
Three questions tell you most of what you need to know about a combination we haven't documented:
Does it carry all your tags? A Content Delivery API response can carry up to 500 cache tags, and the ceiling can come from either the hosting platform or the framework. Among platforms, Cloudflare's
Cache-Tagheader holds roughly 1,000 and Netlify accepts exactly 500, both comfortably above what we emit, as is Fastly'sSurrogate-Key; Vercel caps a cached response at 128 tags, well below our maximum. Among frameworks, Next.js applies that same figure one layer up: it associates at most 128 tags with eachfetch(), so the limit travels with the framework even when you self-host it elsewhere.Does it need extra infrastructure? When tags travel verbatim from our response header into the CDN's header, nothing else is required. When they don't fit, you need a place to store the mapping between what you tagged and what we invalidate — which means a database in the request path.
How quickly can it invalidate? Purge APIs cap the tags per request and rate-limit the calls. Cloudflare accepts 100 tags per purge request on every plan, at 5 requests per minute on Free up to 50 per second on Enterprise. Netlify allows a purge twice every five seconds, per tag or per site. Vercel's bulk endpoint accepts 16 tags per call, the tightest of the three. Since a bulk publish can produce several hundred tags in one webhook delivery, plan on chunking and retrying.
Where the friction shows up
Next.js is the clearest example of the second question mattering. Because a fetch() accepts at most 128 tags, DatoCMS tags cannot be used directly as Next.js tags. Our guide works around this by tagging each query with a single synthetic identifier and keeping a "query ID to cache tags" mapping in a persistent database, written whenever the cache is filled and read whenever the webhook fires. It works, it is fully documented, and there is a starter project for it — but it is a component you will run and operate, and it sits in the invalidation path. Worth being precise about what belongs to whom: the mapping layer is the framework's cost, while the 128-tag ceiling is Vercel's and stays where it is no matter which framework renders the page.
Stacks where the tags reach the CDN untouched avoid that entirely. Astro is a good example on the framework side: its Cache API takes context.cache.set({ maxAge, tags }) and cache.invalidate({ tags }), and the adapter's cache provider forwards the tags to the platform's own header and purge API with no mapping layer in between. The result is the same shape as the plain-HTTP example above, with the platform details abstracted away. Providers exist for Cloudflare, Netlify and Vercel alike — though on Vercel you keep the simplicity and still inherit the 128-tag ceiling.
Worth keeping in mind that what decides the outcome here is mostly the hosting platform, not the framework. Cloudflare, Netlify and Fastly all carry more tags than we emit and expose a purge-by-tag API that any code can call, so on those platforms any framework able to set a response header can integrate cache tags — that is the first half of this page, applied to a managed platform instead of your own CDN. What a framework adds on top is ergonomics: with Astro you never touch a header or a purge endpoint, because its providers wrap those same platform APIs for you.
If you are still choosing, we would point you toward a platform where cache tags stay verbatim end to end and there is room for all 500 of them — Cloudflare, Netlify or Fastly — and then pick whichever framework you would have picked anyway. Both of the costs described above are worth avoiding when you can: a mapping layer is infrastructure you operate, and a tag ceiling below 500 is a limit you will eventually hit without being told.
Our guides
Each of these walks through one stack end to end, from the first query to the invalidation webhook:
DatoCMS Cache Tags and Astro — built on Astro's Cache API, and covering what changes between Cloudflare, Netlify and Vercel.
DatoCMS Cache Tags and Next.js — including the mapping layer that the 128-tag ceiling makes necessary.
If yours isn't here, you are not stuck: the three questions above will tell you most of what to expect from it, and the plain-HTTP integration at the top of this page works with any server that can set a response header, behind any CDN that can purge by tag.