Astro 部署 Cloudflare:用 @astrojs/cloudflare finalize() 与 astro/fetch 管线正确落盘 Cookie 与 CDN 缓存头
2026/9/8 17:31:33 网站建设 项目流程

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 Cloudflarefinalize()response handler for custom request handlers

即:为使用astro/fetch管线编写自定义请求处理器的用户,新增了名为finalize()的 Cloudflare 侧响应收尾函数。调用它可以在返回来自astro/fetch管线的Response之前,统一应用两类信息:

  1. 本次请求期间通过 Astro Cookies API 写入的Set-Cookie响应头;
  2. 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 的实现看,它依次完成:

  1. 懒初始化ensureInitialized()延迟到首次调用才执行setGetEnv(...)createApp()。源码注释说明这样设计是为了避免循环依赖崩溃——自定义fetchFile静态导入本模块时,fetch.ts → astro/app/entrypoint → virtual:astro:fetchable → 用户 worker → fetch.ts形成的环会被打破;
  2. SESSION KV binding 注入:通过injectSessionBinding(app.manifest, env)让会话功能可感知 Cloudflare 的 KV 存储;
  3. 静态资产优先matchStaticAsset(...)命中public/等静态文件时直接返回资产响应;
  4. 无匹配路由时回退到 ASSETS bindinghasMatchingRoute(state)state.routeData?.pattern与原始 pathname 比对,以此区分"真实命中的 404 路由"与"兜底的 404 回退路由",后者把机会让给 ASSETS binding 处理——这正是 changeset 所述"无 Astro 路由匹配时回退静态资产"的具体实现;
  5. 补齐请求上下文:向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.rawenvexecutionCtx)工作,运行时不从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(提供cffinalize)与 packages/integrations/cloudflare/src/hono.ts(提供自动化的cf()中间件),它们在包发布后以@astrojs/cloudflare/fetch@astrojs/cloudflare/hono两个子路径对外暴露。

使用前提与注意事项

基于仓库源码可以总结出以下适用前提,供你在实际项目中判断是否采用finalize()

  1. 仅适用于自定义 fetch handler / Hono 组合管线。传统astro dev/默认 server 入口并不需要手动调用finalize()utils/handler.ts中的默认处理路径同样会调用applyCloudflareResponseHeaders(见 handler.ts),收尾逻辑由框架内置完成;
  2. 务必在cf()之后、return之前调用。若遗漏finalize(),自定义管线产出的响应将缺少 Cookie 与 CDN 缓存默认头;而把finalize()放在cf()返回资产的分支之前也没有意义——资产分支应直接return asset
  3. no-store默认头仅在启用 Cloudflare cache provider 时生效config.cache.provider.name === 'cloudflare'),且不会覆盖你主动声明的Cloudflare-CDN-Cache-Control头;
  4. 懒初始化与不可变头处理都是刻意的设计。不要在模块顶层执行createApp(),也不要假设任何Response的 header 都可直接改写——这两种边界情况官方均已通过延迟初始化与"重建响应"策略规避。

需要查看本次变更的原始记录,可回溯 changeset 文件;想继续深入astro/fetch管线中astropagesmiddlewareactionsi18ncachesessions等其余可组合函数,可阅读 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询