关于 netlify-caching
netlify-caching 是一项面向 Netlify 开发者的技能,说明如何为动态响应和组件配置缓存、变体与失效。内容涵盖 Netlify CDN、Cache API 及缓存调试要点。
主要功能
- 01配置 CDN 缓存控制头
- 02按请求条件区分缓存键
- 03使用标签清除缓存
- 04通过 Cache API 读写响应
- 05检查缓存状态与重新验证
适用场景
- 01缓存 Netlify Functions 的动态响应
- 02按地区、Cookie 或查询参数区分内容
- 03内容更新后按标签清除缓存
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ netlify/ context-and-tools --skill netlify-caching安装后在 Claude Code 中输入/netlify-caching确认已加载。
详细介绍
Netlify 缓存
优先使用的缓存控制标头
动态响应(Functions、Edge Functions、代理)默认不会缓存。必须显式启用缓存。在响应中设置 Netlify-CDN-Cache-Control:
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'
}
});
};
标头选择(最具体的优先;CDN-Cache-Control/Cache-Control 始终会向下游传递):
Netlify-CDN-Cache-Control仅适用于 Netlify CDN。优先使用它。CDN-Cache-Control适用于支持它的所有 CDN。Cache-Control适用于任何 CDN 或浏览器。
应避免的旧方案: On-demand Builders 不支持这些标头或 Netlify-Vary。它们采用 TTL 模式,且只根据 URL 路径生成缓存键。新代码中不要使用 ODB。
易踩的坑(请先阅读)
- 静态资源的缓存有效期最长可达一年。 较短的
max-age会被忽略。只有重新部署或手动清除缓存后,静态资源才会更新。 - 任何页面启用了 basic-auth,都会禁用整个站点的缓存。
durable仅适用于无服务器函数。 它对 Edge Function 响应不起作用。- Cache API 会静默丢弃超过 10 MB 的响应。
cache.put()不会存储这些响应,后续查找也会未命中。请压缩内容或将其拆分为多个条目。 - 切勿让敏感内容退出自动失效机制。部署或防火墙变更后,这些内容可能仍会公开缓存。
指令
public会缓存响应。private仅在浏览器中缓存,不会存入 Netlify 的共享缓存。no-store会阻止缓存。s-maxage=N设置 Netlify 共享缓存中的秒数(在该缓存中优先于max-age)。max-age=N设置任意缓存中的秒数。stale-while-revalidate=N在缓存过期后的 N 秒内提供过期内容,同时在后台重新验证。durable(仅适用于无服务器函数)会将响应存入 Netlify 的持久缓存,以便其他边缘节点重复使用,而不必再次调用该函数。
未设置标头时的默认值:
- 静态:
Netlify-CDN-Cache-Control: public, s-maxage=31536000, must-revalidate - 动态:
Cache-Control: public, max-age=0, must-revalidate
缓存键变化 — Netlify-Vary
响应中的指令以逗号分隔;值列表以竖线分隔:
Netlify-Vary: query=item_id|page, country=es+de|us, cookie=ab_test|is_logged_in
query=a|b按部分查询参数区分;单独使用query则按所有参数区分。键区分大小写;参数顺序不影响结果。header=Device-Type|App-Version支持自定义标头和大多数标准标头。language=en|es+pt使用+表示分组。此设置会根据Accept-Language并结合质量权重进行匹配。country=us|es+pt使用 GeoIP 和 ISO 3166-1 两字母代码;+表示分组。cookie=ab_test|is_logged_in针对指定的键,而非整个Cookie标头。
不能按以下标头区分: Accept*、Cache-Control、Connection、Content-Length、Cookie、Host、If-*、Range、Referer、Upgrade、User-Agent。对于语言、Cookie 或格式,请使用 Vary: Accept-Language/Vary: Cookie,或使用相应的 Netlify-Vary 指令。
一致性规则: 同一 URL 的每个响应都必须返回相同的 Netlify-Vary。第一个缓存响应中的指令优先生效,之后的指令会被忽略。
Netlify-Vary 和标准 Vary 都会生效。格式或编码请使用 Vary,也可用它向 Cloudflare 等上游 CDN 传递指令。
缓存标签与退出自动失效
为响应添加标签,以便按标签清除缓存:
Netlify-Cache-Tag: tag1,tag2,tag3
Netlify-Cache-Tag(Netlify CDN)优先于Cache-Tag(会向下游传递)。有些服务提供商会移除Cache-Tag,因此通过这些提供商代理时,请同时设置两者。- 限制:不区分大小写,仅支持 UTF-8,每个标签不超过 1024 个字符,每个响应最多 500 个标签。
使用 Netlify-Cache-ID 可让响应退出自动原子部署失效机制。值以逗号分隔,会自动注册为可用于清除缓存的标签,并有单独的 500 个 ID 上限:
Netlify-Cache-ID: cms-proxy,product,image
退出后,在相关变更发生时按需清除缓存,例如清除 Netlify-Cache-ID 背后的重定向、代理或函数变更所涉及的缓存。
按需失效(清除缓存)
使用 purgeCache 从已部署的函数中清除缓存。站点 ID 会自动传入:
import { purgeCache } from "@netlify/functions";
export default async () => {
await purgeCache(); // no args = purge everything for the site
return new Response("Purged!", { status: 202 });
};
按标签清除缓存,也可以指定部署版本/子域名:
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 });
};
兼容 Lambda 的函数使用旧版 module.exports.handler = async (event, context) => {…} 签名。必须传入 context.clientContext.custom.purge_api_token:
import { purgeCache } from "@netlify/functions";
module.exports.handler = async (event, context) => {
const token = context.clientContext.custom.purge_api_token;
await purgeCache({ tags: ["tag1", "tag2"], token });
return { body: "Purged!", statusCode: 202 };
};
直接调用 API(在函数外部):发送 POST https://api.netlify.com/api/v1/purge 请求,并设置 Authorization: Bearer <personal_access_token> 和 Content-Type: application/json:
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <personal_access_token>" \
--data '{"site_slug": "mysitename", "cache_tags": ["news"], "deploy_alias": "deploy-preview-11", "domain": "early-access.company.com"}' \
'https://api.netlify.com/api/v1/purge'
- 按站点清除缓存:传入
site_id或site_slug。 - 按标签清除缓存:传入
cache_tags和站点。 - 省略
cache_tags会清除整个站点的缓存。传入空的cache_tags列表则不会清除任何内容。 - 标识符对应关系:在 UI 中(Project configuration > General > Project details > Project information),Project ID =
site_id,Project name =site_slug。请参阅 https://docs.netlify.com/api-and-cli-guides/api-guides/get-started-with-api#get-site。 - 速率限制: 每个标签或站点每 5s 最多只能清除缓存两次。超出限制会返回
429。
Cache API(caches 全局对象)
以编程方式读写来自 Functions/Edge Functions 的 HTTP 响应。可将其用于缓存路由的单个组件或任意 fetch 请求,并与基于标头的路由缓存搭配使用。
作用域规则: 可在任意位置调用 caches.open(),但只能在请求处理程序中调用 match/put/delete。在模块/全局作用域中调用这些方法会抛出异常。
import type { Config, Context } from "@netlify/functions";
const cache = await caches.open("my-cache"); // ok in global scope
export default async (req: Request, context: Context) => {
const request = new Request("https://example.com/expensive-api");
const cached = await cache.match(request);
if (cached) return cached;
const fresh = await fetch(request);
if (fresh.ok) {
cache.put(request, fresh.clone()).catch((error) => {
console.error("Failed to add to the cache:", error);
});
}
return fresh;
};
export const config: Config = { path: "/cache-api-example" };
CacheStorage 子集:
caches.match(request)会从任意缓存中返回一个Response,若无匹配项则返回undefined。caches.open(name)会返回一个Cache。不同名称会拆分缓存并降低命中率,因此应使用少量且有意义的名称。
Cache 方法(均需先调用 caches.open()):
cache.match(request)会返回一个Response,若无匹配项则返回undefined。cache.put(request, response)会添加一个响应。cache.add(request)/cache.addAll(requests)会获取并存储响应。cache.delete(request)会返回true。- 尚未实现
keys(),因此无法列出缓存内容。
一致性:读写操作具有强一致性。删除操作最终一致,因此已删除的条目可能仍会短暂返回。
无法缓存的内容:
- 部分响应(206)、
Vary: *或非GET方法。 - 超过 10 MB 的响应。这些响应不会被存储,且不会显示任何提示。
响应还必须满足以下所有条件,否则存储会报错:
- 包含
max-age/s-maxage≥ 1s 的 cache-control 标头 - 包含
public(不能是private/no-cache/no-store) - 状态码为 2xx
对于无法控制的响应,可使用 fetchWithCache 重写标头。
每次调用的限制: 最多进行 100 次查找和 20 次插入/删除。超过限制后,后续查找都不会返回结果,写入/删除操作也不会执行。每个请求中的 edge 函数共享限制,但无服务器函数和 edge 函数之间的限制彼此独立。
缓存数据按区域存储(不会复制)。重新部署以及 max-age/s-maxage 到期时,缓存会自动失效。
@netlify/cache 模块
安装此模块即可获取辅助工具、时间常量(MINUTE/HOUR/DAY)以及用于本地开发的 caches 导出:
npm install @netlify/cache
本地开发的变通方案: caches 全局对象并非 Node.js 的一部分。Netlify 会在其 Functions/Edge 运行时(包括线上环境和 netlify dev 下)提供该对象。如果使用框架自身的开发服务器,该全局对象未定义,调用时会抛出异常。请改为导入:
import { caches } from "@netlify/cache";
const cache = await caches.open("my-cache");
这需要 Netlify CLI 20.0.3+。本地不会持久化任何内容:查找不会返回结果,写入/删除也不会修改数据。与使用全局对象相比,功能上没有变化。
cacheHeaders(settings) → 标头对象
import { cacheHeaders, DAY } from "@netlify/cache";
const headers = {
"x-custom-header": "some value",
...cacheHeaders({
ttl: 2 * DAY, // s-maxage
swr: HOUR, // stale-while-revalidate
durable: true,
tags: ["product", "sale"],
overrideDeployRevalidation: ["tag"], // opt out of atomic-deploy invalidation
vary: {
cookie: ["ab_test_name", "ab_test_bucket"],
query: ["item_id", "page"], // or true for all
country: ["us", ["es", "pt"]], // nested = OR
language: ["en"],
header: ["Device-Type"],
},
}),
};
如果只需要通用(非 Netlify)标头,请改用 cdn-cache-control npm 模块。
fetchWithCache(resource, options?, cacheSettings?)
可直接替代 fetch,返回缓存的响应;若无缓存,则会获取、存储并返回响应。cacheSettings 会覆盖冲突的响应标头。启用 swr 时,会自动在后台重新验证。
import { fetchWithCache, DAY } from "@netlify/cache";
const response = await fetchWithCache("https://example.com/expensive-api", {
ttl: 2 * DAY,
tags: ["product", "sale"],
vary: { cookie: ["ab_test_name"], query: ["item_id", "page"] },
});
getCacheStatus(response | headers | headerString)
返回 { hit, caches: { durable: { hit, stale, stored, ttl }, edge: { hit, stale } } }。
const { hit, edge, durable } = getCacheStatus(response);
needsRevalidation(response) → boolean
仅当直接调用 cache.match/cache.put 时需要(与 fetchWithCache + swr 配合使用时则不需要)。当 Cache API 响应在其 SWR 窗口内已过期时,该函数会返回 true。先返回过期响应,然后在 context.waitUntil 中重新验证,并通过 cache.put 存储新副本:
if (cached) {
if (needsRevalidation(cached)) {
context.waitUntil(
fetch(request).then((fresh) => {
const response = new Response(fresh.body, {
headers: { ...Object.fromEntries(fresh.headers), ...cacheHeaders({ ttl: MINUTE, swr: HOUR }) },
});
return cache.put(request, response);
})
);
}
return cached;
}
持久缓存
添加 durable(仅适用于无服务器环境),使没有本地副本的边缘节点先检查共享持久缓存,再调用函数。这可以减少调用次数,并缩短缓存未命中的延迟。
- 它最终会保持一致,因此多个区域仍可能针对每个版本调用函数几次。
- 它与站点的函数区域共置。
- 它支持
Netlify-Vary、SWR 和按需失效。 - **Next.js:**Next Runtime 5.5.0+ 会自动使用持久缓存。
使用 Cache-Status 调试
Netlify 会在所有响应中设置 Cache-Status(RFC 9211)。请在已部署的 URL 上检查该标头。查找以 "Netlify Edge" 或 "Netlify Durable" 开头的值:
"Netlify Edge"; fwd=miss表示未缓存任何内容。"Netlify Edge"; hit表示响应由缓存提供。"Netlify Edge"; hit; fwd=stale表示在重新验证期间提供了过期响应(SWR)。"Netlify Durable"; fwd=uri-miss; stored=true; ttl=3600表示持久缓存未命中,随后已存储内容。"Netlify Durable"; hit; ttl=1234表示持久缓存命中。
负数 ttl 表示距离过期已过去的秒数。每个请求可能命中不同的缓存实例。没有生产流量或未启用 durable 时,命中前可能会遇到多个空缓存;重复请求可预热其中一个缓存。
Netlify 缓存规范
以下是组织约定,并非文档事实——它们由 ctx-gen 合并到渲染后的技能中,且不会自动生成。由技能维护者负责维护。
- CDN 只缓存
GET响应。无论标头如何,POST/PUT等请求都不会缓存——请通过GET路由公开可缓存的数据(将输入放在 URL 或查询字符串中)。 - 如果没有
Netlify-Vary: query=...,完整查询字符串会作为缓存键——每个不同的查询字符串(utm_*、fbclid等)都是一个单独的缓存条目。只列出实际会改变响应的参数。 netlify dev不会模拟 CDN 缓存——本地每次都缓存未命中是预期行为,并非错误。请通过已部署 URL(Deploy Preview 或生产环境)的Cache-Status标头验证缓存行为。purgeCache()只有在已部署函数内部才会自动获取凭据。从 CI、本地脚本或构建环境调用时,请传入token(从环境变量读取的个人访问令牌,绝不可硬编码)和siteID。