Astro 项目本地 output: 'static' 构建正常,部署到 Cloudflare 或 Vercel,部署成功,但一半页面 404,另一半白屏没报错。或者反过来:设了 output: 'server' 加了 adapter,astro build 又报 Cannot find adapter for output mode "static" 或 [config] Cannot use prerender = false without an adapter。这就是经典的 Astro adapter 不匹配陷阱。
最快的修法: 先定下一种部署模型,再把三样东西全部对齐到它 —— astro.config.mjs 里的 output 值、是否接了 adapter:、以及每个路由级的 export const prerender = ... 标记。某个页面单独写错一行 prerender,是”线上某路由 404、dev 却正常”最常见的原因。
有一点容易被旧教程误导:从 Astro 5 起(Astro 6 / 2026 年 6 月仍然如此),output: 'hybrid' 已经彻底不存在了。默认的 output: 'static' 本身就支持路由级 export const prerender = false,所谓”hybrid”无非就是 static + 几条 opt-out 路由 + 一个 adapter。如果你的配置里还写着 hybrid,那就是第一个 bug。
常见原因
按 Cloudflare 与 Vercel 上的实际频率排序。
1. 配置里还用着已被移除的 output: 'hybrid'
Astro 5 把 output: 'hybrid' 彻底移除了(不是”弃用”而是”删掉”)。现在只剩两个合法值:output: 'static'(默认;全部静态生成,可在某路由上写 export const prerender = false 让它转为按需渲染)和 output: 'server'(全部按需渲染,可在某路由上写 export const prerender = true)。
如何识别:Astro 5.x / 6.x 项目里 astro.config.mjs 仍有 output: 'hybrid',构建会报 Invalid value for "output" 或提示该值未知。
2. adapter 装了但没接进 astro.config.mjs
npm install @astrojs/cloudflare 只是装了包,你还得 import cloudflare from "@astrojs/cloudflare" 并加上 adapter: cloudflare()。更稳的做法是 npx astro add cloudflare,它会一步装好包、写好 import、设好 output、并脚手架配置。漏了 adapter: 这一行,任何写了 prerender = false 的路由都会在构建时报 Cannot use server-rendered pages without an adapter。
如何识别:构建日志里按需渲染路由数为 0,但代码里明明有 endpoint 文件;或者一碰到 prerender = false 的路由就构建失败。
3. 路由级 export const prerender 与全局模式打架
某个页面写了 export const prerender = false,但全局是 output: 'static' 且没装 adapter —— 构建直接失败。或者你装了 adapter,却以为这页是静态的(其实它现在需要一个静态托管商提供不了的 runtime),结果线上 404、dev 正常。
如何识别:某一个页面线上 404,dev 正常。grep -rn "prerender" src/pages/ 能找到矛盾的那一行。注意:Astro 5+ 的 prerender 只接受字面量 true / false,不再允许动态表达式。
4. adapter 大版本与 Astro 版本对不上
Cloudflare adapter 的 API 跨大版本变化很大。截至 2026 年 6 月,@astrojs/cloudflare 已到 v13.x;Astro 6 要求 v13+,最新的 Astro 5 线也要对应的近期大版本。旧 adapter 配新 Astro(或反过来)要么构建挂、要么产物跑不起来。
如何识别:错误信息里有 is not a function、Adapter API changed、未知的 adapter 选项,或 peer astro@... required。跑 npm ls astro @astrojs/cloudflare 对照检查。
5. 把 Cloudflare adapter 部署到了 Cloudflare Pages(已不再支持)
这是所有照着 2026 年前教程操作的人最容易踩的坑。@astrojs/cloudflare 已经放弃 Cloudflare Pages 支持,现在只面向 Cloudflare Workers(带静态资源托管的 Workers)。把 Pages 项目指向新 adapter 的产物,会出现”部署成功但全 404”,因为 Pages 没法像 wrangler deploy 到 Workers 那样去路由 dist/_worker.js/ 这个 Worker bundle。
如何识别:通过 Pages 的 build / Git 集成部署,构建成功,所有动态路由 404。mode: 'directory'、mode: 'advanced'、functionPerRoute、cloudflareModules 这些旧选项已全部移除 —— 你配置里要是还在传它们,正好印证了你停留在旧的心智模型上。
6. output: 'server' 但部署用错了入口 / 平台没接对
Cloudflare Workers:构建产出 dist/_worker.js/ 加静态资源,用 wrangler deploy 部署,并配一份让 main 指向 worker、绑定 ASSETS 的 wrangler.jsonc / wrangler.toml。Vercel:adapter 产出 .vercel/output 这套 Build Output API 结构。用错入口、或漏了 wrangler 配置,每页都会 404。
如何识别:构建成功,部署也”成功”,但每页返回 404。dist/_worker.js/ 存在,但 wrangler.jsonc 没有 main 或没有 assets 块;或者你把 dist/ 上传到了 Pages,而不是跑 wrangler deploy。
7. 静态预渲染页面里用了仅限 SSR 的 API
Astro.request、Astro.cookies、Astro.redirect、请求头这些都需要按需渲染。在一个被静态预渲染的路由(默认 output: 'static' 且没写 prerender = false)里使用它们,值是在构建时算定的,不是每次请求算 —— 所以线上读到的 cookie / header 是空的或陈旧的。
如何识别:某页面读 cookie / header,dev 正常但线上是空值或构建时的旧值。修法是给该路由加 export const prerender = false(外加一个 adapter)。
8. 平台 runtime 缺了兼容标志
Cloudflare Workers runtime 需要 nodejs_compat 才能 import 你的代码或传递依赖用到的 Node 内置模块(node:buffer、node:crypto、node:path)。Vercel 上则是由项目设置 / package.json 的 engines 控制 Node 版本,而非某个”魔法” adapter 标志。
如何识别:运行时报 Cannot find package 'node:buffer'、No such module "node:...",或 wrangler tail / Workers 日志里出现 Compatibility flag required。
你属于哪一类
| 症状 | 最可能的原因 | 跳到 |
|---|---|---|
astro build 在 output 值上报错 | 已移除的 hybrid(原因 1) | 步骤 2 |
| 构建失败:“cannot use prerender = false without an adapter” | adapter 没接(原因 2) | 步骤 2 |
| 某路由线上 404、dev 正常 | 矛盾的 prerender 标记(原因 3) | 步骤 3 |
构建报错:is not a function / 未知选项 | adapter 与 Astro 版本不匹配(原因 4、5) | 步骤 4 |
| 部署成功,但所有动态路由都 404 | Pages 与 Workers 错位、或入口用错(原因 5、6) | 步骤 5 |
| 仅线上 cookie / header 为空 | 静态页面里用了 SSR API(原因 7) | 步骤 3 |
运行时 Cannot find package 'node:...' | 缺兼容标志(原因 8) | 步骤 6 |
开始排查前
- 拿到准确的 Astro 版本:
npx astro --version。 - 拿到准确的 adapter 包名 + 版本:
npm ls @astrojs/cloudflare(或@astrojs/vercel、@astrojs/node、@astrojs/netlify)。 - 记下
astro.config.mjs的output值和adapter:那一行。 - 查
src/pages/下哪些路由写了export const prerender = ...:grep -rn "prerender" src/pages/。 - 弄清你到底想要哪种部署模型(全静态、全 SSR、还是 static + 个别 opt-out 路由),以及具体哪个托管产品(Cloudflare Workers 而非 Pages、Vercel Functions、Netlify、自建 Node server)。
分步修复
按性价比排序。
步骤 1:先把部署模型定下来,再去对齐其他配置
明确选一种:
- 全静态:
output: 'static'、不装 adapter、任何地方都不写prerender = false,纯静态 HTML,丢到任何静态托管商都行。 - 混合(最常见):全局
output: 'static',在需要按请求运行的路由上写export const prerender = false,安装并接好 adapter。这就是过去说的”hybrid”。 - 全 SSR:
output: 'server'、安装并接好 adapter,真正静态的页面可选写export const prerender = true。
不先做这个决策,下面全是瞎调。
步骤 2:审一遍 astro.config.mjs,对齐到你要的模型
看到 output: 'hybrid' 就改成 output: 'static',靠路由级 prerender = false 来处理 SSR 路由(见步骤 3)。Cloudflare Workers 上的混合模式(Astro 5/6,2026 年 6 月):
// astro.config.mjs
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
export default defineConfig({
output: "static",
adapter: cloudflare(),
site: "https://example.com",
});
Vercel 全 SSR(注意 import 路径现在是裸的 @astrojs/vercel,不再是 @astrojs/vercel/serverless —— 子路径导出已被移除):
import { defineConfig } from "astro/config";
import vercel from "@astrojs/vercel";
export default defineConfig({
output: "server",
adapter: vercel(),
});
添加或修复 adapter 最稳的方式是 npx astro add cloudflare(或 vercel、node、netlify),它会自动设 output、写 import、并按你的 Astro 版本装上匹配的 adapter 大版本。
步骤 3:清理矛盾的 prerender 标记
grep -rn "prerender" src/pages/
逐条判断这个路由是否需要按请求运行:
- 全局
static、这路由需要 SSR —— 保留export const prerender = false,并确认接了 adapter。 - 全局
static、这路由也是静态的 —— 删掉这行。 - 全局
server、这路由可以预先构建 —— 写export const prerender = true。
记住:Astro 5+ 的 prerender 必须是字面量 true / false(不能用变量),而且把它写在 layout 或组件里(而非 page / endpoint 文件)是完全无效的。
步骤 4:让 adapter 大版本匹配 Astro 大版本
npm ls astro
npm ls @astrojs/cloudflare
截至 2026 年 6 月:@astrojs/cloudflare 在 v13.x(Astro 6 要求 v13+);@astrojs/vercel 在 v10.x。与其猜版本号,不如直接重装最新匹配的 adapter:
npm install @astrojs/cloudflare@latest
升大版本前先读 adapter CHANGELOG 里的 breaking change —— Cloudflare adapter 已把旧的 runtime 选项换成了 platformProxy,移除了 mode / functionPerRoute / cloudflareModules,并放弃了 Pages 支持。这类问题导致的 404 详见 astro deploy page not found。
步骤 5:核对构建产物,并把托管平台接对
npm run build
ls dist/
截至 2026 年 6 月各目标的预期产物:
| 目标 | 预期 dist/ 结构 | 部署方式 |
|---|---|---|
| Cloudflare Workers | dist/_worker.js/(一个目录)+ dist/ 里的静态资源 | npx wrangler deploy 配一份 wrangler.jsonc |
| Vercel | dist/.vercel/output/(Build Output API:functions/ + static/) | Vercel 的 build / Git 集成 |
| 全静态 | dist/index.html 一众,没有 _worker.js | 把 dist/ 上传到任意静态托管 |
Cloudflare Workers 的 wrangler.jsonc 必须指向 worker 并绑定资源:
{
"name": "my-astro-site",
"main": "./dist/_worker.js/index.js",
"compatibility_date": "2026-06-18",
"compatibility_flags": ["nodejs_compat"],
"assets": { "binding": "ASSETS", "directory": "./dist" }
}
如果你之前一直在 Cloudflare Pages 上 —— 现在的错位就在这里:新版 adapter 面向的是 Workers。迁移办法是加上上面这份 wrangler.jsonc,改用 wrangler deploy 部署,而不是 Pages 的 Git build。如果暂时离不开 Pages,就把最后一个仍支持 Pages 的 adapter 大版本钉死冻结 —— 但要尽早规划迁到 Workers。
步骤 6:把 Cloudflare 兼容标志放进仓库
把 nodejs_compat 写进签入仓库的 wrangler.jsonc / wrangler.toml(放在 compatibility_flags 下),而不是只配在面板里,这样每次部署、每位协作者都能拿到:
"compatibility_flags": ["nodejs_compat"]
不设它,部署上去的 Worker 没法 import 你的代码(或传递依赖)需要的 Node 内置模块,请求时就会报 No such module "node:..."。
步骤 7:部署一个最小探针路由,确认整条链路打通
加一条最简单的按需 endpoint:
// 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,检查 dist/ 结构和 wrangler.jsonc。
如何确认修好了
npm run build在你预期 SSR 时报告的按需路由数非零,且没有output/prerender报错。dist/结构与步骤 5 表格里部署目标的预期一一对上。- 探针路由在生产返回实时 JSON(不是 404,也不是构建时缓存的值)。
- 之前 404 的路由都能正常打开。
- 读 cookie / header 的路由在生产返回的是每次请求的实时值,而不是空字符串。
wrangler tail(Cloudflare)里没有No such module "node:..."报错。
长期预防
- 把 adapter 大版本钉在
package.json里,与 Astro 主版本同步升级;每次升大版本前读 adapter CHANGELOG。 - CI 加一道检查:grep 统计
prerender指令,发现与全局output矛盾(或写进了 layout / 组件)就让构建失败。 - 始终在线上保留一条探针路由,路由回归一发生就能立刻看到。
- Cloudflare Workers 把
compatibility_flags和compatibility_date写进签入仓库的wrangler.jsonc—— 永远别只依赖面板。 - 在 README 里写清部署模型(以及托管产品是 Workers 而非 Pages),免得后来者随手翻标志。
- 别再想着用
output: 'hybrid',它在 Astro 5/6 里已经不存在了。
常见坑
- 装了 adapter 包却忘了在配置里写
adapter: cloudflare()—— 任何prerender = false路由都会让构建失败。 - 照着 2026 年前的教程,把新版 Cloudflare adapter 部署到了 Cloudflare Pages —— 它现在面向 Workers,动态路由会 404。
- 还在传已被移除的选项(
mode: 'directory'、functionPerRoute、cloudflareModules)并以为它们还有用 —— 实际上要么被静默忽略、要么直接报错。 - import 了
@astrojs/vercel/serverless—— 子路径导出已被移除,直接 import 裸的@astrojs/vercel。 - 把
export const prerender = false粘到 layout 或组件(而非 page / endpoint)里 —— 无效,但你以为修好了。 - 以为
output: 'static'+Astro.cookies没问题因为 dev 能跑 —— dev 永远是按需渲染。症状见 static site blank page。
常见问答
Q: 我配置里还写着 output: 'hybrid',在 Astro 5 或 6 上能构建吗?
不能。Astro 5 已经移除了 hybrid(不是弃用,是删掉)。改用 output: 'static',给需要按需渲染的路由加 export const prerender = false,再配一个 adapter。这套组合正是过去”hybrid”的含义。
Q: 我的 Cloudflare Pages 部署所有动态路由都 404,是不是有什么变了?
是的。@astrojs/cloudflare 已放弃 Cloudflare Pages 支持,现在面向 Cloudflare Workers。加一份 wrangler.jsonc,让 main 指向 ./dist/_worker.js/index.js、把 ASSETS 绑到 ./dist、设上 nodejs_compat,然后用 wrangler deploy 部署,而不是 Pages 的 Git build。
Q: 换 adapter 是不是不用改代码?
大体上,可移植的 API(Astro.request / Astro.cookies)不用改。但平台特有的访问方式不同 —— Cloudflare 的 Astro.locals.runtime.env 与 Node 的 process.env —— 这部分必须改代码。adapter 文档里都列了。
Q: 构建成功但所有路由都 404,从哪查?
先拿步骤 5 的表格对 dist/ 结构。结构跟托管商期望对不上,就是 adapter 目标设错、或 wrangler.jsonc / Vercel 接线没接对。完整 404 故障树见 astro deploy page not found。
Q: 为什么 npm run dev 看不出问题,但部署就坏?
Astro dev 在 Vite 下全部按需渲染,基本忽略 output 和 prerender 标志。生产构建会严格执行它们。合并前务必跑 npm run preview(或 deploy preview)验证。