資源(Static Assets)部署與配置完全指南)
Cloudflare Workers 靜態(tài)資源Static Assets部署與配置完全指南【免費(fèi)下載鏈接】skillsSkills Catalog for Codex項(xiàng)目地址: https://gitcode.com/GitHub_Trending/skills4/skillsCloudflare Workers Static Assetsassets配置 ASSETS綁定是 Cloudflare 在 Workers 上托管靜態(tài)資源的官方能力適用于 SPAReact/Vue/Angular、SSG 站點(diǎn)以及靜態(tài)與動(dòng)態(tài) API 混合的全棧應(yīng)用。讀完本文你將掌握如何在wrangler.jsonc中聲明靜態(tài)資源目錄與路由策略、如何通過(guò)ASSETS綁定在 Worker 代碼中接管請(qǐng)求、SPA/API/鑒權(quán)等高頻模式如何落地以及常見坑與性能優(yōu)化手段。為什么選擇 Workers Static Assets 而不是 PagesCloudflare 提供兩套靜態(tài)托管方案官方參考文檔給出了如下對(duì)照維度Workers Static AssetsCloudflare Pages適用場(chǎng)景混合應(yīng)用靜態(tài)資源 動(dòng)態(tài) API純靜態(tài)站點(diǎn)、SSGWorker 控制力對(duì)路由有完全控制權(quán)有限依賴 Functions配置方式代碼優(yōu)先、靈活wrangler.jsoncGit 驅(qū)動(dòng)、約定優(yōu)先動(dòng)態(tài)路由Worker-first 模式Functions_functions/目錄最適合全棧應(yīng)用、帶 API 的 SPAJamstack、靜態(tài)文檔站決策樹需要自定義路由邏輯→ 選 Workers Static Assets純靜態(tài)站點(diǎn)或 SSG→ 選 PagesAPI 路由 SPA→ 選 Workers Static Assets使用框架Next、Nuxt、Remix→ 選 Pages簡(jiǎn)單來(lái)說(shuō)Workers Static Assets 把靜態(tài)文件變成了 Worker 生態(tài)里一個(gè)可編程的綁定適合需要深度定制請(qǐng)求處理的場(chǎng)景Pages 則把整個(gè)部署流程交給 Git 工作流適合開箱即用??焖匍_始最小可運(yùn)行配置創(chuàng)建項(xiàng)目后只需在wrangler.jsonc中聲明assets.directory并把 Worker 的fetch處理器直接轉(zhuǎn)發(fā)給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就是靜態(tài)資源綁定Fetcher接口的fetch方法接收并轉(zhuǎn)發(fā)任意請(qǐng)求。完整配置說(shuō)明見 configuration.mdAPI 細(xì)節(jié)見 api.md。完整配置項(xiàng)詳解基礎(chǔ)配置最小配置只需要assets.directory一項(xiàng){ name: my-worker, compatibility_date: 2025-01-01, // 新項(xiàng)目建議使用當(dāng)前日期 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/*] } }各配置鍵說(shuō)明鍵類型必填說(shuō)明directorystring?靜態(tài)資源目錄路徑如./dist、./public、./buildbindingstring?Worker 代碼中訪問資源的綁定名如env.ASSETS默認(rèn)ASSETSnot_found_handlingstring?資源未找到時(shí)的行為見下表html_handlingstring?HTML 文件 URL 尾斜杠行為run_worker_firstboolean | string[]?先調(diào)用 Worker 再檢查資源的路徑規(guī)則Wrangler 官方配置參考中還特別指出assets配置已取代舊版site配置成為 Workers 靜態(tài)文件的推薦方式見 wrangler/configuration.md。not_found_handling 模式控制資源不存在時(shí)的兜底行為模式行為適用場(chǎng)景single-page-application對(duì)非資源請(qǐng)求返回/index.htmlReact、Vue、Angular 等 SPA404-page存在則返回/404.html否則返回 404帶自定義錯(cuò)誤頁(yè)的靜態(tài)站點(diǎn)none對(duì)缺失資源直接返回 404API-first 或自定義路由html_handling 模式控制 HTML 文件的尾斜杠行為模式/page/page/適用場(chǎng)景auto-trailing-slash若存在/page/index.html則重定向到/page/直接返回/page/index.html默認(rèn)值SEO 友好force-trailing-slash總是重定向到/page/存在則直接返回統(tǒng)一尾斜杠風(fēng)格drop-trailing-slash存在則直接返回重定向到/page更干凈的 URLnone不做任何修改不做任何修改自定義路由邏輯默認(rèn)值auto-trailing-slashrun_worker_first 配置控制哪些請(qǐng)求先進(jìn)入 Worker 再檢查靜態(tài)資源這是混合應(yīng)用的核心開關(guān)。布爾語(yǔ)法所有請(qǐng)求都先走 Worker{ assets: { run_worker_first: true // 所有請(qǐng)求都調(diào)用 Worker } }數(shù)組語(yǔ)法推薦{ assets: { run_worker_first: [ /api/*, // 正向模式匹配 API 路由 /admin/*, // 匹配管理后臺(tái)路由 !/admin/assets/* // 負(fù)向模式排除管理后臺(tái)靜態(tài)資源 ] } }模式規(guī)則Glob 模式*任意字符、**任意路徑段負(fù)向模式以!前綴排除優(yōu)先級(jí)負(fù)向模式覆蓋正向模式默認(rèn)值false靜態(tài)資源直接由邊緣網(wǎng)絡(luò)響應(yīng)不經(jīng)過(guò) Worker選擇建議API-first 應(yīng)用靜態(tài)資源少→ 用true混合應(yīng)用API 靜態(tài)資源→ 用數(shù)組模式靜態(tài)優(yōu)先站點(diǎn)動(dòng)態(tài)路由極少→ 用false.assetsignore 文件使用與.gitignore相同語(yǔ)法的.assetsignore文件可以排除無(wú)需上傳的文件# .assetsignore _worker.js *.map *.md node_modules/ .git/常見排除項(xiàng)_worker.js- 排除 Worker 代碼混入資源*.map- 排除 source map*.md- 排除 markdown 文件各類開發(fā)期產(chǎn)物Vite 插件集成Vite 項(xiàng)目可使用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 } }) ] });特性開發(fā)期間自動(dòng)檢測(cè)資源資源熱更新HMR生產(chǎn)構(gòu)建集成要求Wrangler 4.0.0、cloudflare/vite-plugin1.0.0關(guān)鍵兼容性日期日期特性影響2025-04-01導(dǎo)航請(qǐng)求優(yōu)化SPA 導(dǎo)航請(qǐng)求跳過(guò) Worker 調(diào)用降低成本新項(xiàng)目建議使用當(dāng)前日期作為compatibility_date。多環(huán)境配置通過(guò)wrangler.jsonc的env字段為不同環(huán)境定制資源策略{ name: my-worker, assets: { directory: ./dist }, env: { staging: { assets: { not_found_handling: 404-page } }, production: { assets: { not_found_handling: single-page-application } } } }部署到指定環(huán)境wrangler deploy --env stagingASSETS 綁定 API 詳解類型定義ASSETS綁定通過(guò)Fetcher接口提供靜態(tài)資源訪問interface Env { ASSETS: Fetcher; } interface Fetcher { fetch(input: RequestInfo | URL, init?: RequestInit): PromiseResponse; }四種調(diào)用方式// 1. 轉(zhuǎn)發(fā)整個(gè)請(qǐng)求 await env.ASSETS.fetch(request); // 2. 字符串路徑主機(jī)名被忽略只有路徑有效 await env.ASSETS.fetch(https://any-host/path/to/asset.png); // 3. URL 對(duì)象 await env.ASSETS.fetch(new URL(/index.html, request.url)); // 4. 構(gòu)造 Request 對(duì)象 await env.ASSETS.fetch(new Request(new URL(/logo.png, request.url), { method: GET, headers: request.headers }));關(guān)鍵行為字符串/URL 輸入的主機(jī)名host/origin會(huì)被忽略只使用路徑方法必須是 GET其他方法返回 405請(qǐng)求頭會(huì)透?jìng)饔绊戫憫?yīng)返回標(biāo)準(zhǔn)Response對(duì)象路徑解析資源相對(duì)于配置的assets.directory解析。以下調(diào)用會(huì)命中同一個(gè)資源env.ASSETS.fetch(https://example.com/logo.png) env.ASSETS.fetch(https://ignored.host/logo.png) env.ASSETS.fetch(/logo.png)影響響應(yīng)的請(qǐng)求頭請(qǐng)求頭作用Accept-Encoding控制壓縮算法gzip、brotliRange支持部分內(nèi)容206 響應(yīng)If-None-Match基于 ETag 的條件請(qǐng)求If-Modified-Since基于修改日期的條件請(qǐng)求自定義請(qǐng)求頭會(huì)透?jìng)鞯挥绊戀Y源服務(wù)。方法支持方法支持響應(yīng)GET?資源內(nèi)容HEAD?僅響應(yīng)頭無(wú)響應(yīng)體POST、PUT等?405 Method Not Allowed響應(yīng)行為Content-Type、緩存與壓縮Content-Type 自動(dòng)推斷根據(jù)文件擴(kuò)展名自動(dòng)設(shè)置擴(kuò)展名Content-Type.htmltext/html; charsetutf-8.csstext/css.jsapplication/javascript.jsonapplication/json.pngimage/png.jpg、.jpegimage/jpeg.svgimage/svgxml.woff2font/woff2默認(rèn)響應(yīng)頭Content-Type: inferred ETag: hash Cache-Control: public, max-age3600 Content-Encoding: br (如果支持且收益明顯)Cache-Control 默認(rèn)值大多數(shù)資源為 1 小時(shí)max-age3600可通過(guò) Worker 響應(yīng)變換覆蓋見下文模式 4。自動(dòng)壓縮基于Accept-Encoding自動(dòng)選擇Brotlibr首選壓縮率最高Gzipgzip兜底方案不壓縮客戶端不支持或資源過(guò)小無(wú)收益ETag 生成ETag 是基于內(nèi)容的哈希用于條件請(qǐng)求If-None-Match。內(nèi)容匹配時(shí)返回304 Not ModifiedETag: a3b2c1d4e5f6...錯(cuò)誤響應(yīng)與處理狀態(tài)碼觸發(fā)條件行為404資源未找到響應(yīng)體取決于not_found_handling配置405非 GET/HEAD 方法{ error: Method not allowed }416Range 頭無(wú)效Range 無(wú)法滿足404 處理邏輯取決于配置// not_found_handling: single-page-application // 返回 /index.html狀態(tài)碼 200 // not_found_handling: 404-page // 存在則返回 /404.html否則返回 404 響應(yīng) // not_found_handling: none // 直接返回 404 響應(yīng)修改響應(yīng)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 } });錯(cuò)誤處理const response await env.ASSETS.fetch(request); if (!response.ok) { // 資源未找到或出錯(cuò) return new Response(Custom error page, { status: 404 }); } return response;條件服務(wù)const url new URL(request.url); // 根據(jù)條件提供不同資源 if (url.pathname /) { return env.ASSETS.fetch(/index.html); } return env.ASSETS.fetch(request);高頻實(shí)戰(zhàn)模式以下模式均來(lái)自 patterns.md可直接復(fù)制使用。1. 轉(zhuǎn)發(fā)請(qǐng)求到資源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. 修改請(qǐng)求后再取資源const url new URL(request.url); url.pathname /index.html; return env.ASSETS.fetch(new Request(url, request));4. 變換資源響應(yīng)追加自定義頭 / 覆蓋緩存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. 條件資源服務(wù)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 路由最常用的全棧模式靜態(tài) 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 } }); }配套配置設(shè)置run_worker_first: [/api/*]讓 API 請(qǐng)求先進(jìn)入 Worker 邏輯。7. 受保護(hù)資源的鑒權(quán)攔截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. 安全響應(yīng)頭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 測(cè)試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. 基于語(yǔ)言的本地化路由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 回調(diào)處理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. 緩存控制覆蓋內(nèi)容哈希文件名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; } };最佳實(shí)踐1. 使用選擇性 Worker-First 路由不要用run_worker_first true改用數(shù)組模式{ assets: { run_worker_first: [ /api/*, // API 路由 /admin/*, // 管理后臺(tái) !/admin/assets/* // 排除后臺(tái)靜態(tài)資源 ] } }收益減少 Worker 調(diào)用次數(shù)、降低成本、提升資源交付性能。2. 利用導(dǎo)航請(qǐng)求優(yōu)化SPA 項(xiàng)目將compatibility_date設(shè)為2025-04-01或更新{ compatibility_date: 2025-04-01, assets: { not_found_handling: single-page-application } }導(dǎo)航請(qǐng)求會(huì)跳過(guò) Worker 調(diào)用降低成本。3. 綁定類型安全始終為環(huán)境聲明類型interface Env { ASSETS: Fetcher; }常見錯(cuò)誤排查Asset not found原因資源不在資源目錄、路徑錯(cuò)誤、資源未部署解決確認(rèn)資源存在、檢查路徑大小寫、必要時(shí)重新部署Worker not invoked for asset原因資源被直接提供run_worker_first未配置解決配置run_worker_first模式以覆蓋對(duì)應(yīng)路由見 configuration.md429 Too Many Requests on free tier原因run_worker_first模式讓大量請(qǐng)求進(jìn)入 Worker觸達(dá)免費(fèi)版限制10 萬(wàn)次/天解決使用更精細(xì)的模式含負(fù)向排除或升級(jí)付費(fèi)計(jì)劃Smart Placement increases latency原因run_worker_firsttrue Smart Placement 將所有請(qǐng)求路由到單個(gè)智能放置點(diǎn)解決使用數(shù)組語(yǔ)法做選擇性匹配或?qū)Y源密集型應(yīng)用關(guān)閉 Smart PlacementCF-Cache-Status header unreliable原因出于隱私原因該頭是概率性添加的解決關(guān)鍵路由邏輯不要依賴CF-Cache-Status改用 ETag、age 等其他信號(hào)JWT expired during deployment原因大型資源部署超過(guò) JWT token 有效期解決升級(jí)到 Wrangler 4.34.0自動(dòng)刷新 token或減少資源數(shù)量Cannot use assets with site原因舊版site配置與新版assets配置沖突解決從site遷移到assets見 configuration.md并從wrangler.jsonc中移除site鍵Assets not updating after deployment原因?yàn)g覽器或 CDN 緩存提供了舊資源解決強(qiáng)制刷新瀏覽器CmdShiftR / CtrlF5使用緩存破壞手段哈希文件名用wrangler tail確認(rèn)部署已完成平臺(tái)限制資源/限制免費(fèi)版付費(fèi)版?zhèn)渥挝募畲篌w積25 MiB25 MiB按文件計(jì)資源總數(shù)20,000100,000需要 Wrangler 4.34.02025 年 9 月Worker 調(diào)用次數(shù)10 萬(wàn)/天1000 萬(wàn)/月用run_worker_first模式優(yōu)化資源存儲(chǔ)無(wú)限無(wú)限已包含版本要求特性最低 Wrangler 版本10 萬(wàn)文件上限付費(fèi)4.34.0Vite 插件4.0.0 cloudflare/vite-plugin 1.0.0導(dǎo)航優(yōu)化4.0.0 compatibility_date: 2025-04-01性能優(yōu)化技巧1. 使用哈希文件名用內(nèi)容哈希文件名開啟長(zhǎng)期緩存app.a3b2c1d4.js styles.e5f6g7h8.css大多數(shù)打包器Vite、Webpack、Parcel會(huì)自動(dòng)生成。2. 最小化 Worker 調(diào)用盡量讓資源直接被邊緣服務(wù){(diào) assets: { // 只為動(dòng)態(tài)路由調(diào)用 Worker run_worker_first: [/api/*, /auth/*] } }3. 充分利用瀏覽器緩存為不同資源設(shè)置合適的Cache-Control// 帶版本號(hào)的資源 Cache-Control: public, max-age31536000, immutable // HTML經(jīng)常校驗(yàn) Cache-Control: public, max-age0, must-revalidate4. 使用 .assetsignore排除非必要文件以縮短上傳時(shí)間*.map *.md .DS_Store node_modules/部署前的準(zhǔn)備工作從 SKILL.md 可知部署前需確認(rèn)認(rèn)證狀態(tài)靜態(tài)資源部署同樣適用npx wrangler whoami # 確認(rèn)已登錄的賬戶本地開發(fā)交互式登錄使用wrangler login一次性 OAuthCI/CD 環(huán)境設(shè)置CLOUDFLARE_API_TOKEN環(huán)境變量詳見 wrangler/auth.md。延伸閱讀本倉(cāng)庫(kù)中與該主題相關(guān)的參考資料configuration.md - 完整配置、路由模式、兼容性日期api.md - ASSETS 綁定 API、請(qǐng)求/響應(yīng)處理patterns.md - 常見模式SPA、API 路由、鑒權(quán)、A/B 測(cè)試等gotchas.md - 限制、錯(cuò)誤、性能建議wrangler/configuration.md - Wrangler 全局配置、環(huán)境與綁定參考【免費(fèi)下載鏈接】skillsSkills Catalog for Codex項(xiàng)目地址: https://gitcode.com/GitHub_Trending/skills4/skills創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考