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 function、Adapter API changed、expected 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.mjs的output值和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.js、server/、functions/,还是只剩 HTML。 - 部署平台:Cloudflare Pages(Functions 还是 Workers?)、Vercel(Serverless 还是 Edge?)、Netlify、Node host。
- 平台侧设置的 runtime 兼容标志。
分步修复
按性价比排序。
步骤 1:先把部署模型定下来,再去对齐其他配置
明确选一种:
- 全静态:
output: 'static'、不装 adapter、任何地方都不写prerender = false,纯静态 HTML 部署。 - 全 SSR:
output: '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 Serverless:
dist/.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 跑,忽略 output 和 prerender 标志。生产构建会严格执行。合并前务必跑 npm run preview 或 deploy preview 验证。