在 Angular 17 及之后的版本里,ng build --prerender=true
背后调用的是 @angular-devkit/build-angular:application
的 prerender builder。只要把 discoverRoutes
关掉,并通过 routesFile
或命令行 --routes-file
指向一份文本清单,构建系统就会 只 生成列表里出现的 HTML。本文围绕这一核心思路,从工作机制、配置要点、到可运行示例与常见陷阱,循序展开,并补充如何用 Node 脚本在 CI 里动态生成 url.txt,以满足上万动态路由的预渲染需求。
Prerender 机制与 routesFile 的角色
Angular 的 prerender 构建阶段会把每个待渲染路由启动一次 Node 版的 @angular/platform-server
,得到静态 HTML,再把资源写进 dist/browser
(blogs.halodoc.io)。为了缩短时间、降低内存,可以关闭自动路径发现 discoverRoutes:false
,改为外部清单驱动(github.com)。官方文档和多篇实践博客都明确指出:
-
builder 读文件时需要纯文本,每行一个相对路径,例如
/about
(angular.ossez.com); -
CLI 同样支持
--routes-file
或驼峰写法--routesFile
,二者等价(onthecode.co.uk); -
若偏好完全命令行,也可用
--routes
直接罗列,但大规模站点更推荐文件方式(natancode.com)。
在 angular.json 中接入 url.txt
下面片段演示怎样让框架只渲染 url.txt:
{
`projects`: {
`demo-app`: {
`architect`: {
`prerender`: {
`builder`: `@angular-devkit/build-angular:application`,
`options`: {
`routesFile`: `url.txt`,
`discoverRoutes`: false
}
}
}
}
}
}
routesFile
指向工程根目录下的 url.txt;如果文件放在 tools/seo/
, 路径可写成 tools/seo/url.txt
。一旦保存,执行
ng build --prerender=true --configuration=production
就会得到只含清单路径的 HTML 输出(dev.to)。
npm script 的可读写方式
有时希望在脚本里临时替换目标文件,例如在 CI Job 中先写入一份新清单再构建,可在 package.json 里这样写:
{
`scripts`: {
`prerender-prod`: `node tools/build-routes.js && ng run demo-app:prerender --routes-file url.txt`
}
}
ng run <project>:prerender
与ng build --prerender=true
最终都会落到同一个 builder(angulararchitects.io)。
可运行的最小示例
假设项目名为 demo-app,创建三个简单页面并准备 url.txt:
ng g c pages/home --standalone
ng g c pages/contact --standalone
ng g c pages/faq --standalone
echo "/" > url.txt
echo "/contact" >> url.txt
echo "/faq" >> url.txt
build 结果:
✔ prerender pages: /, /contact, /faq
3 route(s) prerendered in 2.6 s
浏览 dist/browser/contact/index.html,可看到完整 HTML,且只有 url.txt 中的三个页面被生成(stackoverflow.com)。
动态生成 url.txt 的脚本案例
大型站点往往需要把上千动态产品页写进清单,最简单的做法是:
// tools/build-routes.js
import fs from `fs`;
import fetch from `node-fetch`;
async function main() {
const api = `https://example.com/api/products`;
const products = await fetch(api).then(r => r.json());
const lines = products.map(p => `/product/${p.slug}`);
fs.writeFileSync(`url.txt`, [ `/`, ...lines ].join(`\n`));
}
main();
随后在 CI 中运行 npm run prerender-prod
即可自动抓取最新产品并预渲染对应静态页(reddit.com)。
常见误区与排错技巧
文件路径写错
builder 解析路径相对于项目根目录,若出现 Cannot read routesFile
,请检查 url.txt 是否已提交或工作目录是否正确(github.com)。
routes 开头忘记 /
routes 必须以斜杠起始,否则 prerender 引擎会把它当作文件系统路径,构建阶段直接报错(stackoverflow.com)。
动态参数直接写 :id
在预渲染场景下,/product/:id
不会展开,需要写出具体实例,如 /product/42
;可以用上文 Node 脚本自动枚举(stackoverflow.com)。
发现 HTML 缺少客户端交互
Prerender 生成的静态 HTML 会在浏览器侧通过 Angular hydration 接管,如需保留动画与事件,务必保持组件可在服务器环境中安全执行,比如避免直接访问 window
(blogs.halodoc.io, angular.dev)。
与其他方案对比
方案 | 入口成本 | 任意数量路由 | 首字节时间 | 典型场景 |
---|---|---|---|---|
CSR | 0 | ∞ | 慢 | 内部管理后台 |
SSR | 中 | ∞ | 中 | 经常更新内容 |
Prerender + url.txt | 低 | 受限于列表大小 | 快 | 文档、博客、核心营销页 |
SSG 能带来最接近纯 HTML 的加载速度,同时保持 Angular 生态的一致开发体验(v17.angular.io)。
结尾
借助 routesFile
或 --routes-file
,我们可以把 prerender 的颗粒度精准收敛到 url.txt,并通过脚本自动写入动态路由,在可控的构建时间里获得媲美静态站点的首屏速度。而这一切无需引入额外框架,也不会破坏既有 SSR/Hydration 管线,非常适合渐进式地提升项目 SEO 与性能。
参考链接
-
Angular 官方 SSG 指南
-
Halodoc 生产实践
-
Medium 教程
-
Stack Overflow 问答
-
DEV 社区博文
-
OnTheCode 生成脚本示例
-
AngularArchitects 性能对比
-
Reddit 经验分享
-
GitHub issue 讨论
-
Angular CLI 文档