排错
先记录发生在dev还是生产、当前URL/状态码/Content-Type、浏览器控制台与服务端错误,以及所用构建版本。逐项修正后在同一环境复验;只刷新浏览器不能证明生产资源与服务端manifest已经一致。下方代码是对应位置的诊断片段,假定相关页面、服务和数据已经存在。
目录导航
- Page Not Found
- 服务端代码进入浏览器 bundle
- API 请求收到 HTML
- Layout Data 缺失
- Hydration Mismatch
- 静态资源 404
- 首屏 JS 过大
- Fast Refresh 退回整页刷新
- 当前边界
Page Not Found
如果 res.render("dashboard") 找不到页面,检查:
src/frontend/pages/dashboard.tsx是否存在。- 嵌套页面是否使用完整 id,例如
admin/dashboard。 - 创建文件后 dev/build registry 是否已经重新生成。
同时确认 frontend.enabled、实际配置的页面根与扩展名;默认目录之外不能仅按文件名猜page id。页面存在也不会自动创建URL,需要后端路由调用 res.render()。
复验: 重新dev/build,在实际路由URL请求HTML,确认返回预期页面且日志不再报页面查找失败;分别检查“HTTP路由404”和“已进入handler但render找不到page”,二者修复位置不同。详见路由与页面。
服务端代码进入浏览器 bundle
如果构建提示页面跨越了 server/client 边界,把服务端工作移回 route handler。
根据错误输出的import链追到源头,检查 src/routes/**、src/services/**、src/config/**、node:* 和 *.server.*。把文件改名为shared不会消除服务端依赖。
错误:
正确:
复验: 重新构建确认边界检查通过;检查浏览器实际加载的资源,并验证页面仍能通过route传入数据。不要只删除报错import而遗漏原页面数据需求。
API 请求收到 HTML
API 请求设置 Accept: application/json:
如果使用了 spaFallback.scopes[],把 API 前缀加入 exclude,避免 shell 页面接管 API URL。
这里的Accept用于避免未匹配路径被HTML fallback接管,不能把一个显式返回HTML的handler自动变成JSON。检查真实API路由是否注册、代理是否改写路径,以及全局和scope的exclude;泛化的 */* 仍可能接受HTML。
复验: 用 Accept: application/json 请求实际API和一个不存在的API路径,确认Content-Type及状态码符合接口合同;再用 Accept: text/html 访问SPA scope内有效路径,确认修复没有破坏页面回退。配置说明见CSR与SPA回退。
Layout Data 缺失
通过第三个 render 参数传递 layout data:
不要在 layout 组件里直接 import services。
检查layout是否在当前页面的layout chain中,以及全局 frontend.render.layout 和本次 options.layout 的最终值。layoutData 的键按实际layout id或目录解析,不是任意组件名。
复验: 直接打开页面和通过客户端导航进入页面各验证一次,确认对应layout收到数据且公共layout状态符合预期。详见Layout与组件。
Hydration Mismatch
浏览器第一次 render 要使用和 SSR 一样的输入:
propslayoutDatalocale- 初始
messages
避免在服务端和浏览器产生不同输出的时间戳、随机值、浏览器状态或语言判断。
同时核对服务端与浏览器资源是否来自同一次构建;路由 frontend.hydration: "none" 不执行Vext hydration,不能用它“修复”需要浏览器交互的页面。
复验: 清理该页面的过期缓存后直接访问,再执行导航和交互,确认控制台无新的hydration错误、服务端数据与初次客户端渲染一致。诊断流程见Hydration验证。
静态资源 404
确认引用来源:
支持的图片与字体 import 在 SSR 和浏览器中共用 URL。类型声明只解决 TypeScript 提示,文件缺失、不支持的格式或 URL 部署错误仍需修正。具体用法见Import 型资源、Public 资源与媒体 Image。
如果生产 CDN URL 错误,检查 frontend.deploy.assetBaseUrl、publicPath 和 deploy-manifest.json。
复验: 从生成HTML中复制真实资源URL,分别请求JS/CSS/图片并检查200、正确Content-Type和文件内容;如果配置SRI,同时确认完整性信息对应同一份资源。自定义outDir时检查实际输出目录,避免读取旧dist。详见静态资源与CDN。
首屏 JS 过大
检查实际前端输出中的 size-report.json,默认是 dist/client/size-report.json,并区分入口初始资源与后续按需资源。
常见修复:
- 保持
frontend.i18n.clientLoad="current" - 拆分大型共享组件
- 不要把 admin-only UI import 到根 layout
- React external/CDN 保持 opt-in,并准备 version-lock 与 SRI 策略
- 先检查 route initial assets,再提高预算
复验: 用相同构建参数比较该路由的初始资源与压缩体积,再在浏览器Network检查首次导航是否确实减少资源;预算不超限不等于真实网络或交互性能已经达标。见性能预算。
Fast Refresh 退回整页刷新
模块不是 refresh-safe 时,Fast Refresh 会退回整页刷新。
检查:
frontend.dev.fastRefresh是否开启。- 组件文件是否 import 了服务端模块。
- 文件是否只清晰导出 React 组件。
- 是否修改了
_document.html或 runtime 关键文件。
整页刷新可能是为保持正确性采取的回退,不能只为保留组件状态而绕过它。
复验: 先只改一个普通组件的文本或样式,再单独修改document,观察刷新类型及控制台诊断是否与改动匹配。详见Fast Refresh。
当前边界
当前默认路径:
- React 19 SSR + hydration
- esbuild 前端构建
- route handler
res.render() - Vext JSCSS / CSS Modules / 静态资源
- route-specific modulepreload
- 压缩体积预算
- 兼容页面协议下的客户端导航与公共layout持久化,不兼容时退回document导航
- 本地Image/defineFont媒体处理;远程图片仍需显式loader,框架不自动下载远程字体
已提供但需要显式开启:
- 使用
frontend.render.streaming: "auto"启用 Streaming SSR;默认仍为"buffered"。
当前尚未作为已实现端到端能力提供:
- React Server Components
- Server Functions 与 Server Actions
- partial prerendering(PPR)
- Selective/Partial Hydration、Islands
不要把已支持但需要配置的功能误判为路线图,也不要把Streaming SSR当成RSC。完整边界与规则见边界与路线图。