Pages 与 Workers 静态资源:同一件事的两种做法
读完这篇你能做到:用 wrangler 配置把静态文件和 Worker 代码一起部署,理解请求在什么情况下会进 Worker、什么情况下直接由边缘返回文件,并且能判断手上的项目该放 Pages 还是放 Workers。
两个产品现在都能托管静态文件,配置文件长得很像,额度也不冲突。差别在控制方式和生命周期:Pages 面向"接仓库、自动构建"的工作流,Workers 面向"我自己构建、自己控制每个请求"的工作流。选型的宏观判断在 52-workers-vs-pages.md,这篇讲具体怎么配。
1. 一份配置同时管两者
Workers 的静态资源通过 assets 字段声明,部署时 Worker 代码和文件作为一个整体上传:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-spa",
"main": "src/index.js",
"compatibility_date": "2026-11-10",
"assets": {
"directory": "./dist",
"binding": "ASSETS"
}
}
directory 指向构建产物,binding 声明一个可在代码里引用的绑定名。写完执行 wrangler deploy,一次操作把代码和文件都推上去。
用 Vite 插件时不需要写 directory,插件会自己填。手写配置的项目把这两项写清楚更省心。
2. 请求怎么走
默认规则只有三条,按顺序判断:
- 请求路径能对上
assets目录里的文件,直接返回该文件,Worker 代码不执行。 - 没有对应文件,且存在 Worker 脚本,请求交给 Worker 处理。
- 没有对应文件也没有 Worker 脚本,返回 404。
第 1 条是省额度的关键。命中静态文件时既不跑代码也不消耗 CPU,静态资源请求本身不计量。第 2 条意味着"文件不存在"这件事会把流量导向你的代码,写 catch-all 路由时要知道这一点。
Worker 里主动把请求交回静态资源,用绑定:
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname.startsWith('/api/')) {
return Response.json({ ok: true });
}
return env.ASSETS.fetch(request);
},
};
3. 两个开关改变默认行为
not_found_handling 决定文件不存在时怎么办:
| 取值 | 行为 | 适用 |
|---|---|---|
none | 返回 404,默认值 | 普通多页站点 |
404-page | 返回目录里的 404.html | 有自定义错误页 |
single-page-application | 返回 index.html,由前端路由接管 | React / Vue 的 SPA |
SPA 部署最容易出的问题就是刷新子路由得到 404,设成 single-page-application 就好了。代价是任何不存在的路径都返回 200 加首页,接口路径写错时不会看到 404,只能靠日志发现。
run_worker_first 让请求先过 Worker 再决定是否取静态文件。需要按身份改写页面、做灰度、或在返回 HTML 前注入内容时用它,配成路径数组可以只让部分路径前置。
{
"assets": {
"directory": "./dist",
"binding": "ASSETS",
"not_found_handling": "single-page-application",
"run_worker_first": ["/admin/*"]
}
}
开了这个开关之后,命中的路径每请求都会执行代码,静态请求的免费部分不再适用。
4. 与 Pages 的对照
| 维度 | Pages | Workers 静态资源 |
|---|---|---|
| 构建 | 平台内置,推仓库即构建 | 自己构建,wrangler deploy 上传 |
| 路由控制 | _routes.json,粒度到路径前缀 | run_worker_first、not_found_handling |
| 代码入口 | functions/ 目录,文件即路由 | 单个 main 入口,自己写路由 |
| 预览环境 | 每次部署一个预览域,默认就有 | 靠 wrangler versions 与自定义域 |
| 配置位置 | 控制台为主 | wrangler.jsonc 为主 |
| 适合 | 内容站、要 push 即部署 | 应用、要精确控制请求 |
Pages 的 _routes.json 与 Workers 的 run_worker_first 解决的是同一个问题:哪些请求该进代码。前者按包含排除列表描述,后者按路径前缀前置,思路不同。
官方文档侧栏已经把"从 Pages 迁移到 Workers"单独列出来,Pages 的定位转向了存量项目的维护。新项目如果没有历史包袱,直接在 Workers 上做更合适。
5. 迁移时要改的地方
从 Pages 迁到 Workers,改动集中在四块:
代码入口从 functions/ 的多个文件合成一个 main,路由要自己写。用 Hono 这类框架能省掉手写路由的工作,写法见 58-hono-on-workers.md。
构建从平台搬到本地或 CI,wrangler deploy 之前先跑 npm run build。这一步在 53-first-free-site-with-workers.md 里有完整流程。
绑定从控制台改到配置文件,KV、D1、R2 都写进 wrangler.jsonc。
预览环境需要自己搭。Pages 的"每个分支一个地址"没有直接对应物,常见做法是给每个环境部署一个 Worker,或者用版本加自定义域。
常见问题
两个都配了会怎样。
不会同时生效。一个域名只能指向一个 Worker 或一个 Pages 项目,配了两个是两条独立的路由,请求只会命中其中一条。迁移期间可以并行存在,用不同域名区分。
静态文件为什么也计费了。
检查是否开了 run_worker_first,或者文件路径没对上导致请求落到 Worker。前者是有意为之,后者通常是 directory 填错,实际没有上传任何文件。
Workers 的静态资源有文件数上限吗。
有,单文件 25 MiB 与 Pages 一致,文件数量上限比 Pages 免费档宽松,具体数值随套餐变化。文件多到几万的站点,把资源放 R2 更合适,配额与单文件上限见 56-free-tier-limits-cheatsheet.md。
小结
把静态文件交给 Workers,本质是把 Pages 的托管能力拆成两半:文件上传交给 assets,请求决策交给你写的 fetch。默认规则很省心,命中文件就不跑代码;需要干预时有两个开关,一个管找不到文件时怎么办,一个管哪些路径先过代码。
新项目的判断可以简化成一句:要"推仓库就上线"用 Pages,要"每个请求我说了算"用 Workers。两边都不缺静态托管能力,差的是工作流。
数据来源与检索时间
检索时间 2026-11-10。
| 来源 | URL | 对应数据 |
|---|---|---|
| Workers Static Assets | https://developers.cloudflare.com/workers/static-assets/ | assets.directory / binding 配置、请求命中的三条默认规则、env.ASSETS.fetch 用法、not_found_handling 取值 |
| Pages Limits | https://developers.cloudflare.com/pages/platform/limits/ | 单文件 25 MiB,超大文件建议用 R2 公共桶 |
| Pages Functions Routing | https://developers.cloudflare.com/pages/functions/routing/ | _routes.json 控制调用范围,与 run_worker_first 对照 |
核验说明
- 文档部分:配置字段、三条路由规则、
not_found_handling的三个取值、ASSETS绑定的用法,均来自 2026-11-10 抓取的 Workers Static Assets 页面原文。 - 未执行:本机 Token 只有 Workers 写权限,没有部署过带
assets的 Worker,示例配置未实际下发生效。run_worker_first的数组写法依据文档结构,未验证。 - 未核实:正文提到"Pages 定位转向存量项目维护",依据是官方文档侧栏出现"Migrate to Workers"入口这一观察,未逐字引用官方的生命周期表述。
- 未核实:Workers 静态资源的文件数上限具体数值随套餐变化,正文没有给数字。
- 本地构建验证:无。