Astro 部署 Cloudflare:用 @astrojs/cloudflare finalize() 与 astro/fetch 管线正确落盘 Cookie 与 CDN 缓存头
【免费下载链接】astroThe web framework for content-driven websites. ⭐️ Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/as/astro
导读
当 Astro 应用采用 Cloudflare Workers 自定义入口(custom entrypoint,通常为src/app.ts)配合全新的astro/fetch可组合请求管线时,如何在返回响应前统一补齐 Cookie 写入与 Cloudflare CDN 缓存默认头,是一个容易踩坑的环节。本指南以.changeset/thirty-states-pump.md中记录的@astrojs/cloudflareminor 变更为核心,讲解新增的finalize()响应处理器、它与cf()和astro()的调用关系、Hono 中间件为何能自动完成同一工作,以及静态资产回退与 workerd 预渲染的默认行为。读完你将能在自定义 fetch handler 中写出与官方中间件等价的、安全且完整的响应处理逻辑。
背景:changeset 记录了什么
.changeset/thirty-states-pump.md声明了对@astrojs/cloudflare包的一次minor语义化版本变更,核心内容是:
Adds a Cloudflare
finalize()response handler for custom request handlers
即:为使用astro/fetch管线编写自定义请求处理器的用户,新增了名为finalize()的 Cloudflare 侧响应收尾函数。调用它可以在返回来自astro/fetch管线的Response之前,统一应用两类信息:
- 本次请求期间通过 Astro Cookies API 写入的
Set-Cookie响应头; - Cloudflare CDN 缓存的默认头(
Cloudflare-CDN-Cache-Control)。
自定义 fetch handler 的推荐写法
changeset 中给出的用法示例,是理解本特性最直接的入口。在项目根目录(Cloudflare 集成要求自定义入口文件位于src/app.ts)中:
import { astro, FetchState } from 'astro/fetch'; import { cf, finalize } from '@astrojs/cloudflare/fetch'; export default { async fetch(request: Request, env: Env, context: ExecutionContext) { const state = new FetchState(request); const asset = await cf(state, env, context); if (asset) return asset; return finalize(state, await astro(state)); }, };逐行拆解这段管线的语义:
new FetchState(request)创建本次请求的状态对象。FetchState在 公共 API 入口 中定义,其构造函数会从打包进构建产物的 ambient manifest 读取静态、构建期数据,因此无需自行持有 app 或 pipeline 实例,仅凭一个裸Request即可构造。await cf(state, env, context)完成 Cloudflare 专属的请求前置准备(细节见下文“cf()的前置职责”一节),返回Response表示请求已被 ASSETS binding(静态资产)处理,此时应直接短路返回;返回undefined则继续进入 Astro 渲染。await astro(state)是astro/fetch提供的核心渲染函数。从源码看它等价地委托给handleRequest(state)(见 routing 管线),负责把请求派发给匹配的路由(页面、端点、重定向或 404/500 回退)并生成最终Response。finalize(state, ...)对渲染结果做收尾,然后return给运行时。
finalize() 做了什么:从源码看收尾逻辑
finalize的实现非常简短,位于 Cloudflare 集成 fetch.ts:
/** Applies cookies and Cloudflare CDN cache defaults to an Astro response. */ export function finalize(state: FetchState, response: Response): Response { return applyCloudflareResponseHeaders(response, state.cookies.consume(), cacheProviderEnabled); }它把三样东西交给applyCloudflareResponseHeaders:
response:Astro(或其他上游)已经产出的响应;state.cookies.consume():本次请求期间通过AstroCookies写入的Set-Cookie头集合,一次性取出并消费;cacheProviderEnabled:来自虚拟模块virtual:astro-cloudflare:config的构建期布尔标志。
CDN 缓存默认头的底层规则
真正的头部加工逻辑在 utils/response.ts:
- 若响应中已经存在
Set-Cookie头,则把它们逐个追加到目标响应上; - 只有当
cacheProviderEnabled === true且响应尚未声明Cloudflare-CDN-Cache-Control时,才会补上默认值Cloudflare-CDN-Cache-Control: no-store。源码注释解释了原因:Cloudflare 的 Worker 缓存默认会把未声明缓存意图的 GET 响应缓存至多两小时,因此需要no-store兜底以免无意中被缓存; - 该函数刻意用
try/catch处理"从 Workers Cache API 取出的响应头不可变"这一边界情况:首次修改抛错前未发生任何变更,于是退化为基于new Response(response.body, response)重建一个可写头的响应后重新应用。
cacheProviderEnabled 何时为 true
cacheProviderEnabled并非恒为真,它由构建配置驱动。从 Cloudflare 集成入口 可以看到:needsWorkerCache = config.cache?.provider?.name === 'cloudflare',随后在 同一文件 L454 注入为cacheProviderEnabled: needsWorkerCache。也就是说:只有在astro.config中把 cache provider 配置为 Cloudflare 时,finalize()才会对未声明缓存头的响应附加no-store默认值;未启用该 provider 时finalize()只负责 Cookie 透传。
cf() 的前置职责与静态资产回退
示例中的cf()承担了比finalize()更重的前置工作。从 fetch.ts 的实现看,它依次完成:
- 懒初始化:
ensureInitialized()延迟到首次调用才执行setGetEnv(...)与createApp()。源码注释说明这样设计是为了避免循环依赖崩溃——自定义fetchFile静态导入本模块时,fetch.ts → astro/app/entrypoint → virtual:astro:fetchable → 用户 worker → fetch.ts形成的环会被打破; - SESSION KV binding 注入:通过
injectSessionBinding(app.manifest, env)让会话功能可感知 Cloudflare 的 KV 存储; - 静态资产优先:
matchStaticAsset(...)命中public/等静态文件时直接返回资产响应; - 无匹配路由时回退到 ASSETS binding:
hasMatchingRoute(state)用state.routeData?.pattern与原始 pathname 比对,以此区分"真实命中的 404 路由"与"兜底的 404 回退路由",后者把机会让给 ASSETS binding 处理——这正是 changeset 所述"无 Astro 路由匹配时回退静态资产"的具体实现; - 补齐请求上下文:向
state.locals注入cfContext、设置客户端地址、把ctx.waitUntil挂到renderOptions.waitUntil,并为错误页提供createErrorPageFetch(env)。
完成上述步骤后返回undefined,示意调用方继续执行 Astro 渲染。
Hono 场景:middleware 自动应用
并非所有自定义入口都手动拼装管线。若你使用astro/hono的 Hono 组合方式,则无需手写finalize()——Cloudflare 的 Hono 中间件 会自动完成整套等价逻辑。官方推荐写法为:
import { Hono } from 'hono'; import { actions, middleware, pages, i18n } from 'astro/hono'; import { cf } from '@astrojs/cloudflare/hono'; const app = new Hono<{ Bindings: Env }>(); app.use(cf()); app.use(actions()); app.use(middleware()); app.use(pages()); app.use(i18n()); export default app;cf()中间件(hono.ts L71-L89)的实现值得注意:
- 它通过 duck-typing 的 Hono context(
req.raw、env、executionCtx)工作,运行时不从hono导入任何符号,避免在 Worker 环境引入额外运行时依赖; getFetchState()优先复用已缓存在 context 上的FetchState(键为FETCH_STATE_KEY),没有则基于new FetchState(context.req.raw)新建并回写 context;- 中间件内部直接复用
cfFetch(即上文cf()),命中静态资产即返回资产响应;否则await next()让后续actions()/middleware()/pages()/i18n()依次执行; - 链结束后调用
finalize(state, context.res)。由于 Hono 的 response setter 会 clone 响应并恢复当前响应上的 cookies,若finalize产生了新响应对象,中间件会把Set-Cookie头先取出、赋给新context.res后再逐条追加回去,确保 Cookie 不被 setter 丢弃。
这正是 changeset 中"@astrojs/cloudflare/hono中间件会自动应用这些响应头"一句话背后的完整机制。
其他行为变更:workerd 预渲染与默认入口
changeset 末尾还补充了两条配套行为,可视为同一批 minor 变更的完整性声明:
- 静态资产回退:Cloudflare 自定义入口(custom entrypoint)在没有 Astro 路由匹配请求时,会回退到静态资产(即上文中
fallbackToAssets与 ASSETS binding 的配合); - workerd 预渲染默认入口:当在 workerd 运行时中执行预渲染(prerendering)时,会使用默认的 server entrypoint,而不是自定义入口。
从仓库结构看,这一组新 API 的导出位于 packages/integrations/cloudflare/src/fetch.ts(提供cf与finalize)与 packages/integrations/cloudflare/src/hono.ts(提供自动化的cf()中间件),它们在包发布后以@astrojs/cloudflare/fetch与@astrojs/cloudflare/hono两个子路径对外暴露。
使用前提与注意事项
基于仓库源码可以总结出以下适用前提,供你在实际项目中判断是否采用finalize():
- 仅适用于自定义 fetch handler / Hono 组合管线。传统
astro dev/默认 server 入口并不需要手动调用finalize(),utils/handler.ts中的默认处理路径同样会调用applyCloudflareResponseHeaders(见 handler.ts),收尾逻辑由框架内置完成; - 务必在
cf()之后、return之前调用。若遗漏finalize(),自定义管线产出的响应将缺少 Cookie 与 CDN 缓存默认头;而把finalize()放在cf()返回资产的分支之前也没有意义——资产分支应直接return asset; no-store默认头仅在启用 Cloudflare cache provider 时生效(config.cache.provider.name === 'cloudflare'),且不会覆盖你主动声明的Cloudflare-CDN-Cache-Control头;- 懒初始化与不可变头处理都是刻意的设计。不要在模块顶层执行
createApp(),也不要假设任何Response的 header 都可直接改写——这两种边界情况官方均已通过延迟初始化与"重建响应"策略规避。
需要查看本次变更的原始记录,可回溯 changeset 文件;想继续深入astro/fetch管线中astro、pages、middleware、actions、i18n、cache、sessions等其余可组合函数,可阅读 astro/fetch 公共入口,FetchState的完整公开契约则定义于 fetch-state.ts。
【免费下载链接】astroThe web framework for content-driven websites. ⭐️ Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/as/astro
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考