Astro adapter 与 SSR/SSG 模式不匹配 —— 排查与修复

Astro 部署失败或页面白屏,往往是 adapter 与 output 模式对不上,路由级 prerender 又把全局模式给覆盖了。

Astro 项目本地 output: 'static' 构建正常,部署到 Cloudflare Pages 或 Vercel,部署成功,但一半页面 404,另一半白屏没报错。或者反过来,设了 output: 'server' 加了 Node adapter,astro build 又报 Cannot find adapter for output mode "static"。这就是经典的 Astro adapter 不匹配陷阱。Astro 4/5 把模型改得更正交(output: 'static' | 'server' | 'hybrid',或 Astro 5 里更统一的 output: 'static' | 'server' + 路由级 prerender),但部署目标的 adapter 必须对上,单个路由的 prerender 标记也会悄悄推翻全局模式。

常见原因

按 Cloudflare Pages 与 Vercel 上的实际频率排序。

1. Astro 5 已经统一 output 模型,但配置里还混着 hybrid

Astro 5 弃用了 output: 'hybrid'。新做法是 output: 'static' + 在动态路由上写 export const prerender = false(或反过来 output: 'server' + 静态路由上写 prerender = true)。一些配置还留着 hybrid,构建会默默选错模式。

如何识别:Astro 5.x 项目的 astro.config.mjs 里仍有 output: 'hybrid'。构建日志会有被淹没掉的废弃警告。

2. adapter 装了但没接进 astro.config.mjs

npm install @astrojs/cloudflare 只是装了包,还得 import cloudflare from "@astrojs/cloudflare" 加上 adapter: cloudflare()。漏了的话 Astro 按静态默认构建,SSR 路由直接变 404。

如何识别:构建日志里 (SSR routes) 计数是 0,但代码里明明有 endpoint。Cloudflare Pages Functions 标签页里看不到任何 function。

3. 路由级 prerender = true/false 与全局模式打架

某个页面写了 export const prerender = false,但全局是 output: 'static'。Astro 把它当 SSR 来生成,但静态部署目标根本没有 SSR runtime,线上 404。

如何识别:某一个页面线上 404,dev 正常。grep -r "prerender" src/pages/ 能找到矛盾的一行。

4. adapter 版本与 Astro 版本对不上

@astrojs/cloudflare@9@astrojs/cloudflare@12 是两套 API。Astro 5 必须用新版 adapter,Astro 4 只能用旧版。版本混着用要么构建挂、要么产物跑不起来。

如何识别:错误信息里有 is not a functionAdapter API changedexpected APIContext to have ...。用 npm ls @astrojs/cloudflare 对照 Astro 版本检查。

5. output: 'server' 但生成的入口产物不是目标平台要的

部分 adapter 需要指定 mode: 'directory'mode: 'advanced'(Cloudflare),或 edge: true / false(Vercel)。默认 mode 生成的文件结构跟平台期望的对不上。

如何识别:构建成功,Cloudflare Pages 部署成功,每页都 404。dist/_worker.js 缺失,或 dist/functions/[[path]].js 缺失,看你期望的是哪种 mode。

6. 静态页面里用了 Astro.cookies / Astro.request

这俩 API 需要 SSR。在 output: 'static' 的页面里用,构建默默不产物化它们 —— 页面是渲染了,但读到的全是 undefined。

如何识别:某页面读 cookie/header,dev 正常但线上空值。构建日志会有 Astro.request is not available in static pages 之类的提示。

7. 平台 runtime 版本低于 adapter 最低要求

Cloudflare adapter 需要 Workers 兼容标志 nodejs_compat;Vercel Node 20 adapter 需要在 adapter options 里设 runtime。漏掉就会出现”产物有但平台跑不起来”。

如何识别:部署日志 Cannot find package 'node:buffer'Compatibility flag required

开始排查前

  • 拿到 Astro 版本:npx astro --version
  • 拿到 adapter 包名 + 版本:npm ls @astrojs/cloudflare(或 vercel/node/netlify)。
  • 记下 astro.config.mjsoutput 值和 adapter: 那一行。
  • src/pages/ 下有哪些文件写了 export const prerender = ...
  • 弄清楚你到底想要哪种部署模型:全静态、全 SSR、还是路由级混合。

需要收集的信息

  • 完整的 astro.config.mjs 内容。
  • find src/pages -name "*.astro" -o -name "*.ts" | xargs grep -l "prerender" 的结果。
  • 构建末尾汇总:静态路由数、SSR 路由数、endpoint 数。
  • 本地构建后 dist/ 的目录结构:有没有 _worker.jsserver/functions/,还是只剩 HTML。
  • 部署平台:Cloudflare Pages(Functions 还是 Workers?)、Vercel(Serverless 还是 Edge?)、Netlify、Node host。
  • 平台侧设置的 runtime 兼容标志。

分步修复

按性价比排序。

步骤 1:先把部署模型定下来,再去对齐其他配置

明确选一种:

  • 全静态output: 'static'、不装 adapter、任何地方都不写 prerender = false,纯静态 HTML 部署。
  • 全 SSRoutput: 'server'、安装并接好 adapter,真正静态的页面可选写 prerender = true
  • 混合(Astro 5+):全局 output: 'static',SSR 页面上 export const prerender = false,安装并接好 adapter。

不先做这个决策,后面都是瞎调。

步骤 2:审一遍 astro.config.mjs,对齐到你要的模型

Astro 5 + Cloudflare Pages 混合模式:

// astro.config.mjs
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";

export default defineConfig({
  output: "static",
  adapter: cloudflare({ mode: "directory" }),
  site: "https://example.com",
});

Vercel Serverless 全 SSR:

import { defineConfig } from "astro/config";
import vercel from "@astrojs/vercel/serverless";

export default defineConfig({
  output: "server",
  adapter: vercel(),
});

步骤 3:清理矛盾的 prerender 标记

grep -rn "prerender" src/pages/

逐条决定:这页面要 SSR 吗?全局是 static 而这页要 SSR,保留 export const prerender = false;全局 static 这页也是 static,删掉这行;全局 server 这页想预渲染,写 export const prerender = true

步骤 4:让 adapter 版本匹配 Astro 版本

npm ls astro
npm ls @astrojs/cloudflare

Astro 5.x 装 12.x 系列 adapter:

npm install @astrojs/cloudflare@latest

读一下 adapter CHANGELOG 里的 breaking change(mode 选项、runtime flag)。这种问题导致的 404 详见 astro deploy page not found

步骤 5:核对构建产物结构是不是平台预期的样子

npm run build
ls dist/

各目标的预期产物:

  • Cloudflare Pages,mode: 'directory'dist/_worker.js/ 目录 + dist/_routes.json
  • Cloudflare Pages,mode: 'advanced'dist/_worker.js 单文件。
  • Vercel Serverlessdist/.vercel/output/functions/ + dist/.vercel/output/static/
  • 全静态dist/index.html 一众,没有 _worker.js

跟期望对不上,adapter 配置就是错的。

步骤 6:在平台侧补上 runtime 兼容标志

Cloudflare Pages → Settings → Functions → Compatibility flags:

nodejs_compat

Vercel Node adapter 的 options:

vercel({
  runtime: "nodejs20.x",
  webAnalytics: { enabled: false },
});

不设这些,部署上去的 worker / function 没法 import 你的代码(或传递依赖)需要的 Node 内置模块。

步骤 7:部署一个最小探针路由,确认链路打通

加一条最简单的动态路由:

// src/pages/probe.json.ts
export const prerender = false;
export const GET = ({ request }) => {
  return new Response(
    JSON.stringify({ ok: true, url: request.url }),
    { headers: { "content-type": "application/json" } },
  );
};

部署后访问 /probe.json。能拿到 JSON 就说明 SSR 路由通了;如果还是 404,说明 adapter 还没接对,回到步骤 5。

验证

  • npm run build 报告的 SSR 路由数与你的预期一致。
  • dist/ 目录结构和部署目标的预期一一对上。
  • 探针路由在生产能返回实时 JSON。
  • 之前 404 的路由都能正常打开。
  • 构建日志里没有 Astro.request is not available 警告。

长期预防

  • 把 adapter 版本明确钉在 package.json 里,与 Astro 主版本同步升级。
  • CI 加一道检查:grep 统计 prerender 指令,并断言它与全局 output 不冲突。
  • 在 README 里写清楚部署模型,避免后来者随手翻标志。
  • 始终在线上保留一条探针路由,回归发生时立刻能看到。
  • Cloudflare Pages 的兼容标志放进仓库里的 wrangler.toml,别只配在面板里。
  • Astro 5+ 别再用 output: 'hybrid',迁移很机械化。

常见坑

  • 装了 adapter 包却忘了在配置里 adapter: cloudflare() —— Astro 静默按静态构建,SSR 路由消失。
  • Cloudflare 上设 mode: 'directory',但你的 Astro 版本只支持 mode: 'advanced' —— 产物 Cloudflare 没法 route。
  • export const prerender = false 粘到 layout 文件(不是 page)里 —— 无效,但你以为修好了。
  • 以为 output: 'static' + Astro.cookies 没问题因为 dev 能跑 —— dev 全部是 SSR,生产静态直接 drop API。症状见 static site blank page
  • 部署到 Vercel 却用 Cloudflare adapter “因为都是边缘” —— 产物结构完全不一样。

常见问答

Q: Astro 5 里用 output: 'hybrid' 能跑通,是不是没事?

短期它是 static + 路由级 prerender 的别名,能跑。但官方已废弃,后续 minor 版本会移除。趁早迁移到 output: 'static' + SSR 路由显式 prerender = false

Q: 换 adapter 是不是不用改代码?

Astro.request / Astro.cookies 这类大体兼容;但平台特有 API(Cloudflare 的 context.locals.runtime.env 与 Node 的 process.env)一定要改代码,adapter 文档里都列了。

Q: 构建成功但所有路由都 404,从哪查?

先看 dist/ 目录结构。结构和平台期望对不上,说明 adapter mode 设错了。完整 404 故障树见 astro deploy page not found

Q: 为什么 npm run dev 看不出问题,但部署就坏?

Astro dev 在 Vite 下全部以 SSR 跑,忽略 outputprerender 标志。生产构建会严格执行。合并前务必跑 npm run preview 或 deploy preview 验证。

标签: #排查 #Astro #adapter #ssr #ssg