Skip to content
Deployment
Skill

/netlify-caching

Cache dynamic and static responses on Netlify's CDN from Functions, Edge Functions, and proxies. Use when you add caching or cache-control headers to a function response, tune cache TTL or stale-while-revalidate, set up the durable cache, vary a cache key by

From plugin
netlify-skills
3715 skills1 MCP
Install
$ npx -y skills add netlify/context-and-tools --skill netlify-caching --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • 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.

Cache dynamic and static responses on Netlify's CDN from Functions, Edge Functions, and proxies. Use when you add caching or cache-control headers to a function response, tune cache TTL or stale-while-revalidate, set up the durable cache, vary a cache key by

SKILL.md

netlify-caching.SKILL.md
name: netlify-caching
description: Cache dynamic and static responses on Netlify's CDN from Functions, Edge Functions, and proxies. Use when you add caching or cache-control headers to a function response, tune cache TTL or stale-while-revalidate, set up the durable cache, vary a cache key by query/header/cookie/country/language, purge or invalidate the cache by site or cache tag, use the programmatic Cache API (caches.open/match/put) or @netlify/cache helpers (fetchWithCache/cacheHeaders/getCacheStatus), speed up an expensive API call, add ISR or on-demand revalidation, or debug why a response is or isn't cached via the Cache-Status header.

Netlify caching

Cache-control header to reach for

Dynamic responses (Functions, Edge Functions, proxies) are **NOT cached by default** — you must opt in. Set `Netlify-CDN-Cache-Control` on the response:

import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  return new Response("Hello world", {
    headers: {
      'Netlify-CDN-Cache-Control': 'public, durable, max-age=60, stale-while-revalidate=120'
    }
  });
};

Header choice (most specific wins; `CDN-Cache-Control`/`Cache-Control` always pass downstream):

  • `Netlify-CDN-Cache-Control` — Netlify CDN only. **Reach for this.**
  • `CDN-Cache-Control` — all CDNs that support it.
  • `Cache-Control` — any CDN or the browser.

**Legacy path to avoid:** On-demand Builders do **not** support these headers or `Netlify-Vary` — they use a TTL pattern and key on URL path only. Don't reach for ODBs in new code.

Footguns (read first)

  • **Only `GET` is cached.** POST/PUT/etc. are never cached regardless of headers — expose cacheable data on a GET route (inputs in the URL or query string).
  • **`netlify dev` does not emulate the CDN cache.** A local cache miss every time is expected. Verify caching on a deployed URL (Deploy Preview or production) via its `Cache-Status` header.
  • **Without `Netlify-Vary: query=...`, the full query string is the cache key** — every distinct query string (`utm_*`, `fbclid`, …) is a separate cache entry. Enumerate only the params that change the response.
  • **Static assets are fresh for up to a year** — a shorter `max-age` is ignored. They change only on a new deploy or manual purge.
  • **basic-auth on ANY page disables caching for the ENTIRE site.**
  • **`durable` is serverless-only** — it has no effect on Edge Function responses.
  • Never opt sensitive content out of automatic invalidation — it can stay publicly cached after deploys/firewall changes.

Directives

  • `public` cache it / `private` browser-only, not Netlify's shared cache / `no-store` don't cache.
  • `s-maxage=N` seconds in Netlify's shared cache (overrides `max-age` there).
  • `max-age=N` seconds in any cache.
  • `stale-while-revalidate=N` serve stale for N seconds after expiry while revalidating in background.
  • `durable` (serverless only) store in Netlify's durable cache so other edge nodes reuse it instead of re-invoking the function.

Defaults when no header is set — static: `Netlify-CDN-Cache-Control: public, s-maxage=31536000, must-revalidate`; dynamic: `Cache-Control: public, max-age=0, must-revalidate`.

Cache key variation — `Netlify-Vary`

Comma-delimited instructions on the response; pipe-delimited value lists:

Netlify-Vary: query=item_id|page, country=es+de|us, cookie=ab_test|is_logged_in
  • `query=a|b` subset, or bare `query` for all params. Keys case-sensitive; param order irrelevant.
  • `header=Device-Type|App-Version` — custom + most standard headers.
  • `language=en|es+pt` — `+` groups; checked against `Accept-Language` with quality weighting.
  • `country=us|es+pt` — GeoIP, ISO 3166-1 two-letter codes; `+` groups.
  • `cookie=ab_test|is_logged_in` — target specific keys, not the whole `Cookie` header.

**Cannot vary by header on:** `Accept*`, `Cache-Control`, `Connection`, `Content-Length`, `Cookie`, `Host`, `If-*`, `Range`, `Referer`, `Upgrade`, `User-Agent`. For language/cookie/format use `Vary: Accept-Language`/`Vary: Cookie` or the specific `Netlify-Vary` instruction.

**Consistency rule:** a URL must return the same `Netlify-Vary` on every response — the first cached response's instructions win and later ones are ignored. `Netlify-Vary` + standard `Vary` are both respected (use `Vary` for format/encoding, and to pass instructions to an upstream CDN like Cloudflare).

Cache tags & opt-out

Tag responses for taggable purging:

Netlify-Cache-Tag: tag1,tag2,tag3
  • `Netlify-Cache-Tag` (Netlify CDN) wins over `Cache-Tag` (passed downstream). Some providers strip `Cache-Tag` — set both when proxying through them.
  • Constraints: case-insensitive, UTF-8 only, ≤1024 chars/tag, ≤500 tags/response.

Opt a response out of automatic atomic-deploy invalidation with `Netlify-Cache-ID` (comma-separated; auto-registered as cache tags for purging; separate 500-ID limit):

Netlify-Cache-ID: cms-proxy,product,image

After opting out, purge on-demand after relevant changes (e.g. redirect/proxy or function changes behind a `Netlify-Cache-ID`).

On-demand invalidation (purge)

Purge from a **deployed function** with `purgeCache` (site ID is passed automatically):

import { purgeCache } from "@netlify/functions";

export default async () => {
  await purgeCache(); // no args = purge everything for the site
  return new Response("Purged!", { status: 202 });
};

Purge by tag, optionally targeting a deploy/subdomain:

import { purgeCache } from "@netlify/functions";

export default async (req: Request) => {
  const cacheTag = new URL(req.url).searchParams.get("tag");
  if (!cacheTag) return;
  await purgeCache({
    tags: [cacheTag],
    deployAlias: "deploy-preview-11",
    domain: "early-access.company.com",
  });
  return new Response("Purged!", { status: 202 });
};

**Ambient credentials only work inside a deployed function.** From CI, local scripts, or the build, pass `token` (a personal

Read more
Ships withnetlify-skills

Public Netlify skills for AI coding agents. Each skill is a focused, factual reference for a Netlify platform primitive — designed to help agents build correctly on Netlify without needing to search docs.

Get the whole plugin

Other skills on netlify-skills.