内容协商是从单个 URL 提供资源不同版本的做法,根据最终用户定制体验。常见示例包括以特定语言提供内容(Accept-Language)、为设备优化(User-Agent)或提供现代图片格式(Accept)。
Cloudflare 的全球网络旨在大规模处理此需求。对于提供下一代图片等常见场景,此协商通过专用功能简化。对于更自定义的逻辑,Cloudflare 提供包括 Transform Rules、Snippets、Custom Cache Keys 和 Workers 在内的工具包,为您提供细粒度控制,确保每次向每位用户提供正确内容。
Transform Rule 方法在您能创建不同 URL 时很理想,例如根据访问者位置提供内容。
在此示例中,您运营电子商务站点,希望根据访问者国家以当地货币显示价格。
-
在 Cloudflare 仪表板中,前往规则的 Overview(概览) 页面。
Go to Overview ↗ -
选择 Create rule(创建规则),然后选择 URL Rewrite Rule(URL 重写规则) 选项。
-
输入描述性名称,例如
Vary by Country - Canada。 -
在 If incoming requests match...(如果传入请求匹配……) 中,选择 Custom filter expression(自定义过滤表达式)。
-
在 When incoming requests match...(当传入请求匹配……) 下,创建以下表达式:
- Field(字段):
Country - Operator(运算符):
equals - Value(值):
Canada
- Field(字段):
-
在 Then...(则……) 下:
- 对于 Path(路径),选择 Preserve(保留)。
- 对于 Query(查询),选择 Rewrite to(重写为):Dynamic(动态)
loc=ca
-
选择 Save(保存)。
现在,来自加拿大对 /products/item 的请求将在到达源站或缓存之前被转换为 /products/item?loc=ca,从而创建独立的缓存条目。
图片 Vary 告知 Cloudflare 您的源站支持哪些变体。Cloudflare 随后分别缓存每个版本,并在不每次联系源站的情况下向浏览器提供正确版本。此功能通过 Cloudflare API 进行管理。
要启用此功能,请使用 API 创建 variants rule。此规则将文件扩展名映射到源站可提供的图像格式。
例如,以下 API 调用告知 Cloudflare,对于 .jpeg 和 .jpg 文件,您的源站可以提供 image/webp 和 image/avif 变体:
Required API token permissions
At least one of the following token permissions is required:Zone Settings WriteZone Write
curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/cache/variants" \
--request PATCH \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
--json '{
"value": {
"jpeg": [
"image/webp",
"image/avif"
],
"jpg": [
"image/webp",
"image/avif"
]
}
}'创建规则后,Cloudflare 将为每个图像变体创建独立的缓存条目,从而提升现代浏览器用户的性能。
Snippets 是独立的 JavaScript fetch 处理程序,在边缘节点上运行,处理经过 Cloudflare 的请求。它们允许您以编程方式与缓存交互,提供对缓存键和响应行为的完全控制,而无需更改用户可见的 URL。
在本示例中,您运行由名为 ab-test 的 cookie(值为 group-a 或 group-b)控制的 A/B 测试。您希望为每个组缓存不同版本的页面。
-
在 Cloudflare 仪表板中,前往 Snippets(代码片段) 页面。
Go to Snippets ↗ -
选择 Create Snippet(创建代码片段) 并命名为
ab-test-caching。 -
粘贴以下代码。它根据
ab-testcookie 修改缓存键,并将响应缓存 30 天。
const CACHE_DURATION = 30 * 24 * 60 * 60; // 30 天
export default {
async fetch(request) {
// 根据 A/B cookie 构造新的缓存键 URL
const abCookie = request.headers.get('Cookie')?.match(/ab-test=([^;]+)/)?.[1] || 'control';
const url = new URL(request.url);
url.pathname = `/ab-test/${abCookie}${url.pathname}`;
const cacheKey = new Request(url, request);
const cache = caches.default;
let response = await cache.match(cacheKey);
if (!response) {
// 如果缓存中没有,从源站获取
response = await fetch(request);
response = new Response(response.body, response);
response.headers.set("Cache-Control", `s-maxage=${CACHE_DURATION}`);
// 将响应以自定义键存入缓存
await cache.put(cacheKey, response.clone());
}
return response;
},
};- 保存并部署 Snippet。
- 在 Snippets 仪表板中,选择 Attach to route(附加到路由) 以分配该 Snippet。
如果您的账户使用企业版计划,自定义缓存键功能提供无代码界面,用于定义缓存键中包含哪些请求属性。
自定义缓存键选项:
- 按设备类型缓存
- 查询字符串选项
No query string parameters except - 包含标头和值
- 包含 cookie 名称和值
- 用户:设备类型、国家/地区、语言
如果您的源站根据 Accept 标头在同一 URL 提供不同内容类型(例如 application/json 与 text/html),请使用自定义缓存键分别缓存它们。
-
在 Cloudflare 仪表板中,前往 Cache Rules(缓存规则) 页面。
Go to Cache Rules ↗ -
选择 Create rule(创建规则)。
-
输入规则名称,例如
Vary by Accept Header。 -
设置规则的适用条件(例如特定主机名或路径)。
-
在 Cache key(缓存键) 下,选择 Use custom cache key(使用自定义键)。
-
选择 Add new item(添加新项)。
- Type(类型):
Header - Name(名称):
Accept - Value(值):添加每个
value,或留空表示全部。
- Type(类型):
-
选择 Deploy(部署)。
此配置根据 Accept 标头值创建独立的缓存条目,遵循您的 API 的内容协商。
对于复杂的缓存场景,Cloudflare Workers 提供完整的无服务器环境,非常适合在大规模场景下实现自定义逻辑。
此 Worker 检测访客使用的是移动设备还是桌面设备,并为每种设备创建独立的缓存条目,确保提供并缓存正确版本的站点。
export default {
async fetch(request, env, ctx) {
const userAgent = request.headers.get('User-Agent') || '';
const deviceType = userAgent.includes('Mobile') ? 'mobile' : 'desktop';
// 创建包含设备类型的新缓存键 URL
const url = new URL(request.url);
url.pathname = `/${deviceType}${url.pathname}`;
const cacheKey = new Request(url, request);
const cache = caches.default;
let response = await cache.match(cacheKey);
if (!response) {
console.log(`Cache miss for ${deviceType} device. Fetching from origin.`);
response = await fetch(request);
let responseToCache = response.clone();
ctx.waitUntil(cache.put(cacheKey, responseToCache));
}
return response;
},
};此 Worker 检测访客使用的是移动设备还是桌面设备,并为每种设备创建独立的缓存条目,确保提供并缓存正确版本的站点。使用企业版 cf.customCacheKey 功能。
export default {
async fetch(request) {
// 1. 从 User-Agent 标头确定设备类型
const userAgent = request.headers.get('User-Agent') || '';
const deviceType = userAgent.includes('Mobile') ? 'mobile' : 'desktop';
// 2. 通过在 URL 后附加设备类型来创建自定义缓存键
const customCacheKey = `${request.url}-${deviceType}`;
// 3. 获取响应。Cloudflare 的缓存自动使用
// customCacheKey 进行缓存操作(match、put)。
const response = await fetch(request, {
cf: {
cacheKey: customCacheKey,
},
});
// 可选:在返回响应之前修改它
// 例如,添加标头以指示使用了哪个缓存键
const newResponse = new Response(response.body, response);
newResponse.headers.set("X-Cache-Key", customCacheKey);
return newResponse;
},
};一个常见的挑战是缓存来自 Next.js 等框架的内容。Next.js 使用 RSC(React Server Components)请求标头来区分同一 URL 的 HTML 页面加载和 RSC 数据载荷。以下是处理此问题的最佳方法。
最简单的解决方案是创建一条 Transform Rule,检查 RSC 标头并在请求上添加唯一查询参数,从而创建两个不同的可缓存 URL:/page(用于 HTML)和 /page?_rsc=1(用于 RSC 载荷)。
-
在 Cloudflare 仪表板中,前往规则的 Overview(概览) 页面。
Go to Overview ↗ -
选择 Create rule(创建规则),然后选择 URL Rewrite Rule(URL 重写规则) 选项。
-
输入名称,例如
Vary by RSC Header。 -
在 If incoming requests match(如果传入请求匹配) 中,选择 Custom filter expression(自定义过滤表达式)。
-
在 When incoming requests match(当传入请求匹配) 下,手动编辑表达式以检查
RSC标头是否存在:has_key(http.request.headers, "rsc")
-
在 Then(则) 下:
- 对于 Path(路径),选择 Preserve(保留)。
- 对于 Query(查询),选择 Rewrite to(重写为),选择 Static(静态):
_rsc=1。
-
选择 Save(保存)。
或者,使用 Snippets 或自定义缓存键将 RSC 标头直接添加到缓存键,而不修改可见 URL。这样可以保持更简洁的 URL,但需要更高级的配置。