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

Astro 部署失败或页面白屏,多半是 adapter 跟 output 模式对不上,或某个路由的 prerender 标记悄悄推翻了全局模式。本文给出诊断与修复(Astro 5/6,2026 年 6 月)。

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 functionAdapter 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'functionPerRoutecloudflareModules 这些旧选项已全部移除 —— 你配置里要是还在传它们,正好印证了你停留在旧的心智模型上。

6. output: 'server' 但部署用错了入口 / 平台没接对

Cloudflare Workers:构建产出 dist/_worker.js/ 加静态资源,用 wrangler deploy 部署,并配一份让 main 指向 worker、绑定 ASSETSwrangler.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.requestAstro.cookiesAstro.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:buffernode:cryptonode:path)。Vercel 上则是由项目设置 / package.json 的 engines 控制 Node 版本,而非某个”魔法” adapter 标志。

如何识别:运行时报 Cannot find package 'node:buffer'No such module "node:...",或 wrangler tail / Workers 日志里出现 Compatibility flag required

你属于哪一类

症状最可能的原因跳到
astro buildoutput 值上报错已移除的 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
部署成功,但所有动态路由都 404Pages 与 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.mjsoutput 值和 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”。
  • 全 SSRoutput: '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(或 vercelnodenetlify),它会自动设 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/cloudflarev13.x(Astro 6 要求 v13+);@astrojs/vercelv10.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 Workersdist/_worker.js/(一个目录)+ dist/ 里的静态资源npx wrangler deploy 配一份 wrangler.jsonc
Verceldist/.vercel/output/(Build Output API:functions/ + static/Vercel 的 build / Git 集成
全静态dist/index.html 一众,没有 _worker.jsdist/ 上传到任意静态托管

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_flagscompatibility_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'functionPerRoutecloudflareModules)并以为它们还有用 —— 实际上要么被静默忽略、要么直接报错。
  • 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 下全部按需渲染,基本忽略 outputprerender 标志。生产构建会严格执行它们。合并前务必跑 npm run preview(或 deploy preview)验证。

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