Cloudflare Workers 静态资源(Static Assets)部署与配置完全指南
Cloudflare Workers 静态资源Static Assets部署与配置完全指南【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skillsCloudflare Workers Static Assetsassets配置 ASSETS绑定是 Cloudflare 在 Workers 上托管静态资源的官方能力适用于 SPAReact/Vue/Angular、SSG 站点以及静态与动态 API 混合的全栈应用。读完本文你将掌握如何在wrangler.jsonc中声明静态资源目录与路由策略、如何通过ASSETS绑定在 Worker 代码中接管请求、SPA/API/鉴权等高频模式如何落地以及常见坑与性能优化手段。为什么选择 Workers Static Assets 而不是 PagesCloudflare 提供两套静态托管方案官方参考文档给出了如下对照维度Workers Static AssetsCloudflare Pages适用场景混合应用静态资源 动态 API纯静态站点、SSGWorker 控制力对路由有完全控制权有限依赖 Functions配置方式代码优先、灵活wrangler.jsoncGit 驱动、约定优先动态路由Worker-first 模式Functions_functions/目录最适合全栈应用、带 API 的 SPAJamstack、静态文档站决策树需要自定义路由逻辑→ 选 Workers Static Assets纯静态站点或 SSG→ 选 PagesAPI 路由 SPA→ 选 Workers Static Assets使用框架Next、Nuxt、Remix→ 选 Pages简单来说Workers Static Assets 把静态文件变成了 Worker 生态里一个可编程的绑定适合需要深度定制请求处理的场景Pages 则把整个部署流程交给 Git 工作流适合开箱即用。快速开始最小可运行配置创建项目后只需在wrangler.jsonc中声明assets.directory并把 Worker 的fetch处理器直接转发给ASSETS绑定// wrangler.jsonc { name: my-app, main: src/index.ts, compatibility_date: 2025-01-01, assets: { directory: ./dist } }// src/index.ts export default { async fetch(request: Request, env: Env): PromiseResponse { return env.ASSETS.fetch(request); } };部署命令只需一条wrangler deploy其中env.ASSETS就是静态资源绑定Fetcher接口的fetch方法接收并转发任意请求。完整配置说明见 configuration.mdAPI 细节见 api.md。完整配置项详解基础配置最小配置只需要assets.directory一项{ name: my-worker, compatibility_date: 2025-01-01, // 新项目建议使用当前日期 assets: { directory: ./dist } }全部配置键{ name: my-worker, main: src/index.ts, compatibility_date: 2025-01-01, assets: { directory: ./dist, binding: ASSETS, not_found_handling: single-page-application, html_handling: auto-trailing-slash, run_worker_first: [/api/*, !/api/docs/*] } }各配置键说明键类型必填说明directorystring✅静态资源目录路径如./dist、./public、./buildbindingstring❌Worker 代码中访问资源的绑定名如env.ASSETS默认ASSETSnot_found_handlingstring❌资源未找到时的行为见下表html_handlingstring❌HTML 文件 URL 尾斜杠行为run_worker_firstboolean | string[]❌先调用 Worker 再检查资源的路径规则Wrangler 官方配置参考中还特别指出assets配置已取代旧版site配置成为 Workers 静态文件的推荐方式见 wrangler/configuration.md。not_found_handling 模式控制资源不存在时的兜底行为模式行为适用场景single-page-application对非资源请求返回/index.htmlReact、Vue、Angular 等 SPA404-page存在则返回/404.html否则返回 404带自定义错误页的静态站点none对缺失资源直接返回 404API-first 或自定义路由html_handling 模式控制 HTML 文件的尾斜杠行为模式/page/page/适用场景auto-trailing-slash若存在/page/index.html则重定向到/page/直接返回/page/index.html默认值SEO 友好force-trailing-slash总是重定向到/page/存在则直接返回统一尾斜杠风格drop-trailing-slash存在则直接返回重定向到/page更干净的 URLnone不做任何修改不做任何修改自定义路由逻辑默认值auto-trailing-slashrun_worker_first 配置控制哪些请求先进入 Worker 再检查静态资源这是混合应用的核心开关。布尔语法所有请求都先走 Worker{ assets: { run_worker_first: true // 所有请求都调用 Worker } }数组语法推荐{ assets: { run_worker_first: [ /api/*, // 正向模式匹配 API 路由 /admin/*, // 匹配管理后台路由 !/admin/assets/* // 负向模式排除管理后台静态资源 ] } }模式规则Glob 模式*任意字符、**任意路径段负向模式以!前缀排除优先级负向模式覆盖正向模式默认值false静态资源直接由边缘网络响应不经过 Worker选择建议API-first 应用静态资源少→ 用true混合应用API 静态资源→ 用数组模式静态优先站点动态路由极少→ 用false.assetsignore 文件使用与.gitignore相同语法的.assetsignore文件可以排除无需上传的文件# .assetsignore _worker.js *.map *.md node_modules/ .git/常见排除项_worker.js- 排除 Worker 代码混入资源*.map- 排除 source map*.md- 排除 markdown 文件各类开发期产物Vite 插件集成Vite 项目可使用cloudflare/vite-plugin// vite.config.ts import { defineConfig } from vite; import { cloudflare } from cloudflare/vite-plugin; export default defineConfig({ plugins: [ cloudflare({ assets: { directory: ./dist, binding: ASSETS } }) ] });特性开发期间自动检测资源资源热更新HMR生产构建集成要求Wrangler 4.0.0、cloudflare/vite-plugin1.0.0关键兼容性日期日期特性影响2025-04-01导航请求优化SPA 导航请求跳过 Worker 调用降低成本新项目建议使用当前日期作为compatibility_date。多环境配置通过wrangler.jsonc的env字段为不同环境定制资源策略{ name: my-worker, assets: { directory: ./dist }, env: { staging: { assets: { not_found_handling: 404-page } }, production: { assets: { not_found_handling: single-page-application } } } }部署到指定环境wrangler deploy --env stagingASSETS 绑定 API 详解类型定义ASSETS绑定通过Fetcher接口提供静态资源访问interface Env { ASSETS: Fetcher; } interface Fetcher { fetch(input: RequestInfo | URL, init?: RequestInit): PromiseResponse; }四种调用方式// 1. 转发整个请求 await env.ASSETS.fetch(request); // 2. 字符串路径主机名被忽略只有路径有效 await env.ASSETS.fetch(https://any-host/path/to/asset.png); // 3. URL 对象 await env.ASSETS.fetch(new URL(/index.html, request.url)); // 4. 构造 Request 对象 await env.ASSETS.fetch(new Request(new URL(/logo.png, request.url), { method: GET, headers: request.headers }));关键行为字符串/URL 输入的主机名host/origin会被忽略只使用路径方法必须是 GET其他方法返回 405请求头会透传影响响应返回标准Response对象路径解析资源相对于配置的assets.directory解析。以下调用会命中同一个资源env.ASSETS.fetch(https://example.com/logo.png) env.ASSETS.fetch(https://ignored.host/logo.png) env.ASSETS.fetch(/logo.png)影响响应的请求头请求头作用Accept-Encoding控制压缩算法gzip、brotliRange支持部分内容206 响应If-None-Match基于 ETag 的条件请求If-Modified-Since基于修改日期的条件请求自定义请求头会透传但不影响资源服务。方法支持方法支持响应GET✅资源内容HEAD✅仅响应头无响应体POST、PUT等❌405 Method Not Allowed响应行为Content-Type、缓存与压缩Content-Type 自动推断根据文件扩展名自动设置扩展名Content-Type.htmltext/html; charsetutf-8.csstext/css.jsapplication/javascript.jsonapplication/json.pngimage/png.jpg、.jpegimage/jpeg.svgimage/svgxml.woff2font/woff2默认响应头Content-Type: inferred ETag: hash Cache-Control: public, max-age3600 Content-Encoding: br (如果支持且收益明显)Cache-Control 默认值大多数资源为 1 小时max-age3600可通过 Worker 响应变换覆盖见下文模式 4。自动压缩基于Accept-Encoding自动选择Brotlibr首选压缩率最高Gzipgzip兜底方案不压缩客户端不支持或资源过小无收益ETag 生成ETag 是基于内容的哈希用于条件请求If-None-Match。内容匹配时返回304 Not ModifiedETag: a3b2c1d4e5f6...错误响应与处理状态码触发条件行为404资源未找到响应体取决于not_found_handling配置405非 GET/HEAD 方法{ error: Method not allowed }416Range 头无效Range 无法满足404 处理逻辑取决于配置// not_found_handling: single-page-application // 返回 /index.html状态码 200 // not_found_handling: 404-page // 存在则返回 /404.html否则返回 404 响应 // not_found_handling: none // 直接返回 404 响应修改响应const response await env.ASSETS.fetch(request); // 克隆并修改 return new Response(response.body, { status: response.status, headers: { ...Object.fromEntries(response.headers), Cache-Control: public, max-age31536000, X-Custom: value } });错误处理const response await env.ASSETS.fetch(request); if (!response.ok) { // 资源未找到或出错 return new Response(Custom error page, { status: 404 }); } return response;条件服务const url new URL(request.url); // 根据条件提供不同资源 if (url.pathname /) { return env.ASSETS.fetch(/index.html); } return env.ASSETS.fetch(request);高频实战模式以下模式均来自 patterns.md可直接复制使用。1. 转发请求到资源export default { async fetch(request: Request, env: Env): PromiseResponse { return env.ASSETS.fetch(request); } };2. 按路径获取指定资源const response await env.ASSETS.fetch(https://assets.local/logo.png);3. 修改请求后再取资源const url new URL(request.url); url.pathname /index.html; return env.ASSETS.fetch(new Request(url, request));4. 变换资源响应追加自定义头 / 覆盖缓存const response await env.ASSETS.fetch(request); const modifiedResponse new Response(response.body, response); modifiedResponse.headers.set(X-Custom-Header, value); modifiedResponse.headers.set(Cache-Control, public, max-age3600); return modifiedResponse;5. 条件资源服务export default { async fetch(request: Request, env: Env): PromiseResponse { const url new URL(request.url); if (url.pathname /) { return env.ASSETS.fetch(/index.html); } return env.ASSETS.fetch(request); } };6. SPA API 路由最常用的全栈模式静态 SPA 与后端 API 共存export default { async fetch(request: Request, env: Env): PromiseResponse { const url new URL(request.url); if (url.pathname.startsWith(/api/)) { return handleAPI(request, env); } return env.ASSETS.fetch(request); } }; async function handleAPI(request: Request, env: Env): PromiseResponse { return new Response(JSON.stringify({ status: ok }), { headers: { Content-Type: application/json } }); }配套配置设置run_worker_first: [/api/*]让 API 请求先进入 Worker 逻辑。7. 受保护资源的鉴权拦截export default { async fetch(request: Request, env: Env): PromiseResponse { const url new URL(request.url); if (url.pathname.startsWith(/admin/)) { const session await validateSession(request, env); if (!session) { return Response.redirect(/login, 302); } } return env.ASSETS.fetch(request); } };配套配置run_worker_first: [/admin/*]8. 安全响应头export default { async fetch(request: Request, env: Env): PromiseResponse { const response await env.ASSETS.fetch(request); const secureResponse new Response(response.body, response); secureResponse.headers.set(X-Frame-Options, DENY); secureResponse.headers.set(X-Content-Type-Options, nosniff); secureResponse.headers.set(Content-Security-Policy, default-src self); return secureResponse; } };9. 基于 Cookie 的 A/B 测试export default { async fetch(request: Request, env: Env): PromiseResponse { const cookies request.headers.get(Cookie) || ; const variant cookies.includes(variantb) ? b : a; const url new URL(request.url); if (url.pathname /) { return env.ASSETS.fetch(/index-${variant}.html); } return env.ASSETS.fetch(request); } };10. 基于语言的本地化路由export default { async fetch(request: Request, env: Env): PromiseResponse { const locale request.headers.get(Accept-Language)?.split(,)[0] || en; const url new URL(request.url); if (url.pathname /) { return env.ASSETS.fetch(/${locale}/index.html); } if (!url.pathname.startsWith(/${locale}/)) { url.pathname /${locale}${url.pathname}; } return env.ASSETS.fetch(url); } };11. OAuth 回调处理export default { async fetch(request: Request, env: Env): PromiseResponse { const url new URL(request.url); if (url.pathname /auth/callback) { const code url.searchParams.get(code); if (code) { const session await exchangeCode(code, env); return new Response(null, { status: 302, headers: { Location: /, Set-Cookie: session${session}; HttpOnly; Secure; SameSiteLax } }); } } return env.ASSETS.fetch(request); } };配套配置run_worker_first: [/auth/*]12. 缓存控制覆盖内容哈希文件名export default { async fetch(request: Request, env: Env): PromiseResponse { const response await env.ASSETS.fetch(request); const url new URL(request.url); // 不可变资源带哈希文件名 if (/\.[a-f0-9]{8,}\.(js|css|png|jpg)$/.test(url.pathname)) { return new Response(response.body, { ...response, headers: { ...Object.fromEntries(response.headers), Cache-Control: public, max-age31536000, immutable } }); } return response; } };最佳实践1. 使用选择性 Worker-First 路由不要用run_worker_first true改用数组模式{ assets: { run_worker_first: [ /api/*, // API 路由 /admin/*, // 管理后台 !/admin/assets/* // 排除后台静态资源 ] } }收益减少 Worker 调用次数、降低成本、提升资源交付性能。2. 利用导航请求优化SPA 项目将compatibility_date设为2025-04-01或更新{ compatibility_date: 2025-04-01, assets: { not_found_handling: single-page-application } }导航请求会跳过 Worker 调用降低成本。3. 绑定类型安全始终为环境声明类型interface Env { ASSETS: Fetcher; }常见错误排查Asset not found原因资源不在资源目录、路径错误、资源未部署解决确认资源存在、检查路径大小写、必要时重新部署Worker not invoked for asset原因资源被直接提供run_worker_first未配置解决配置run_worker_first模式以覆盖对应路由见 configuration.md429 Too Many Requests on free tier原因run_worker_first模式让大量请求进入 Worker触达免费版限制10 万次/天解决使用更精细的模式含负向排除或升级付费计划Smart Placement increases latency原因run_worker_firsttrue Smart Placement 将所有请求路由到单个智能放置点解决使用数组语法做选择性匹配或对资源密集型应用关闭 Smart PlacementCF-Cache-Status header unreliable原因出于隐私原因该头是概率性添加的解决关键路由逻辑不要依赖CF-Cache-Status改用 ETag、age 等其他信号JWT expired during deployment原因大型资源部署超过 JWT token 有效期解决升级到 Wrangler 4.34.0自动刷新 token或减少资源数量Cannot use assets with site原因旧版site配置与新版assets配置冲突解决从site迁移到assets见 configuration.md并从wrangler.jsonc中移除site键Assets not updating after deployment原因浏览器或 CDN 缓存提供了旧资源解决强制刷新浏览器CmdShiftR / CtrlF5使用缓存破坏手段哈希文件名用wrangler tail确认部署已完成平台限制资源/限制免费版付费版备注单文件最大体积25 MiB25 MiB按文件计资源总数20,000100,000需要 Wrangler 4.34.02025 年 9 月Worker 调用次数10 万/天1000 万/月用run_worker_first模式优化资源存储无限无限已包含版本要求特性最低 Wrangler 版本10 万文件上限付费4.34.0Vite 插件4.0.0 cloudflare/vite-plugin 1.0.0导航优化4.0.0 compatibility_date: 2025-04-01性能优化技巧1. 使用哈希文件名用内容哈希文件名开启长期缓存app.a3b2c1d4.js styles.e5f6g7h8.css大多数打包器Vite、Webpack、Parcel会自动生成。2. 最小化 Worker 调用尽量让资源直接被边缘服务{ assets: { // 只为动态路由调用 Worker run_worker_first: [/api/*, /auth/*] } }3. 充分利用浏览器缓存为不同资源设置合适的Cache-Control// 带版本号的资源 Cache-Control: public, max-age31536000, immutable // HTML经常校验 Cache-Control: public, max-age0, must-revalidate4. 使用 .assetsignore排除非必要文件以缩短上传时间*.map *.md .DS_Store node_modules/部署前的准备工作从 SKILL.md 可知部署前需确认认证状态静态资源部署同样适用npx wrangler whoami # 确认已登录的账户本地开发交互式登录使用wrangler login一次性 OAuthCI/CD 环境设置CLOUDFLARE_API_TOKEN环境变量详见 wrangler/auth.md。延伸阅读本仓库中与该主题相关的参考资料configuration.md - 完整配置、路由模式、兼容性日期api.md - ASSETS 绑定 API、请求/响应处理patterns.md - 常见模式SPA、API 路由、鉴权、A/B 测试等gotchas.md - 限制、错误、性能建议wrangler/configuration.md - Wrangler 全局配置、环境与绑定参考【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考