Installs just this skill. Get the whole plugin for auto-invocation.
โก How it fires
How this skill gets triggered: by you, by Claude, or both.
Fires itselfClaude auto-loads it when your prompt matches the work.
You can call itInvoke it directly when you want it.
Slash command/netlify-caching
๐๏ธ Context preview
The summary Claude sees to decide when to auto-load this skill.
Guide for controlling caching on Netlify's CDN. Use when configuring cache headers, setting up stale-while-revalidate, implementing on-demand cache purge, or understanding Netlify's CDN caching behavior. Covers Cache-Control, Netlify-CDN-Cache-Control, cache tags, durable cache,
๐ Stats
Stars27
Forks8
LanguageTypeScript
LicenseMIT
๐ฆ Ships with netlify-skills
</> SKILL.md
netlify-caching.SKILL.md
---name: netlify-caching
description: Guide for controlling caching on Netlify's CDN. Use when configuring cache headers, setting up stale-while-revalidate, implementing on-demand cache purge, or understanding Netlify's CDN caching behavior. Covers Cache-Control, Netlify-CDN-Cache-Control, cache tags, durable cache, and framework-specific caching patterns.
---# Caching on Netlify
## Default Behavior
**Static assets** are cached automatically:
- CDN: cached for 1 year, invalidated on every deploy
- Browser: always revalidates (`max-age=0, must-revalidate`)
- No configuration needed
**Dynamic responses** (functions, edge functions, proxied) are **not cached by default**. Add cache headers explicitly.
**Only `GET` requests are cached.** Netlify's CDN caches responses to `GET` requests only. Responses to `POST`, `PUT`, `PATCH`, `DELETE`, and other non-`GET` methods are never cached, no matter what cache headers you set on them. If a response needs to be CDN-cacheable, expose it on a `GET` route (put the inputs in the URL/query string) โ you cannot make the CDN cache a mutating `POST` endpoint by adding cache headers.
## Cache-Control Headers
Three headers control caching, from most to least specific:
| Header | Who sees it | Use case |
|---|---|---|
| `Netlify-CDN-Cache-Control` | Netlify CDN only (stripped before browser) | CDN-only caching |
| `CDN-Cache-Control` | All CDN caches (stripped before browser) | Multi-CDN setups |
`purgeCache()` picks up the site ID and credentials automatically **only when it runs inside a deployed Netlify Function**. Called from anywhere else โ a local script, a CI job, a build step, or any code outside the Netlify Functions runtime โ it has no ambient credentials, and you must pass a Netlify personal access token (and the site ID). Read the token from an environment variable; never hardcode it:
```typescript
await purgeCache({
token: process.env.NETLIFY_PURGE_TOKEN, // a Netlify personal access token, from env
siteID: process.env.NETLIFY_SITE_ID,
tags: ["product"],
});
```
`Netlify-Cache-Tag` is **purge-only**: tagged responses are still cleared by automatic deploy-based invalidation like everything else. The tag only lets you purge them on demand between deploys.
### Surviving deploys with `Netlify-Cache-ID`
To keep a cached response *across* deploys, set a `Netlify-Cache-ID` header. A response carrying it is **excluded from automatic deploy-based invalidation** โ it persists across deploys and clears only on explicit purge. The `Netlify-Cache-ID` value also auto-registers as a purge tag, so you purge it by that same id:
// Purge by the same id โ it doubles as a purge tag.
await purgeCache({ tags: ["catalog"] });
```
## Cache Key Variation
`Netlify-Vary` controls what creates separate cache entries. Each directive names a dimension โ `query`, `header`, `cookie`, `country`, or `language` โ followed by an enumerated, pipe-separated list of the values to key on:
```typescript
return new Response(body, {
headers: {
"Netlify-Vary": "cookie=ab_test|is_logged_in",
},
});
```
- `query=param1|param2` โ key on the named query parameters
- `header=X-Custom` โ key on the named request header
- `cookie=ab_test|is_logged_in` โ key on the named cookies
- `country=us|de` โ serve a distinct cached entry to visitors from the listed countries (two-letter, lowercase ISO country codes)
- `language=en|fr` โ key on the listed `Accept-Language` values
Combine dimensions by separating directives with commas โ e.g. `Netlify-Vary: query=theme, cookie=plan` keys the cache on both the `theme` query parameter and the `plan` cookie. Always enumerate the specific values; keying on an entire dimension (for example a bare `Vary: Cookie`) fragments the cache into a separate entry per unique visitor.
Full control over cache headers in server routes. Set `Netlify-CDN-Cache-Control` in responses for CDN caching.
### Nuxt
Default Nitro preset handles caching. ISR-style patterns use `routeRules` with `swr` or `isr` options.
### Vite SPA
Static assets are cached by default. API responses from Netlify Functions need explicit cache headers.
**The full query string is part of the cache key by default.** With no `Netlify-Vary: query=` directive, every distinct query string is cached as a separate entry โ so appending tracking or marketing params (`?utm_source=โฆ`, `fbclid`, and the like) silently fragments the cache into many near-duplicate entries and lowers the hit rate, even when those params don't change the response. Add `Netlify-Vary: query=<names>` listing only the parameters that actually affect the output; the CDN then keys on just those and ignores all other query params, collapsing the variants onto one cache entry.
## Local Development
`netlify dev` does not emulate the CDN cache. Header-based CDN caching is only observable on a deployed site: locally, cache headers pass through untouched, nothing is stored in or served from the CDN cache, Cache-API reads return no persisted entries, and the `Cache-Status` response header is absent. A "cache miss every time" on `localhost` is expected, not a bug โ you cannot validate `Netlify-Vary` keying, cache tags, durable cache, or purge behavior locally. Verify caching on a deployed URL instead (a Deploy Preview or production) and read its `Cache-Status` header.
## Debugging
Check the `Cache-Status` response header. Netlify emits it in the RFC 9211 format โ one entry per named cache layer the request passed through, not a bare `HIT`/`MISS`: