angular-ssr 是做什么的?
使用 @angular/ssr 在 Angular v20+ 中实现服务端渲染和水合。用于 SSR 设置、水合策略、静态页面预渲染以及处理仅浏览器 API。触发条件包括 SSR 配置、修复水合不匹配、预渲染路由或使代码兼容 SSR。
要求
- Angular v20+(推荐 v21+)
@angular/ssr包(在angular.json中配置serverbuilder 时默认包含)
设置 SSR
-
将 SSR 功能添加到一个新的 Angular 工作区:
ng new my-app --ssr -
或者,将 SSR 添加到一个现有的应用:
ng add @angular/ssr
水合
SSR 默认启用 provideClientHydration(),但您可以通过从 @angular/platform-browser 导入并调用 provideClientHydration() 来明确启用它。
在您的 app.config.ts 中:
import { ApplicationConfig } from '@angular/core';
import { provideClientHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [provideClientHydration()]
};
水合不匹配
水合不匹配会导致控制台错误。常见原因包括:
- 动态内容:使用服务器和客户端不同步的日期、随机数。使用
ngSkipHydration跳过元素的水合。 - 不可预见的 DOM 结构:避免在 SSR 期间修改 DOM。
- 第三方脚本:确保第三方库在 SSR 期间不操作 DOM。
修复不匹配的步骤:
- 检查浏览器控制台中类似
Hydration node mismatch的错误。 - 使用
provideClientHydration()的withDomEventsReplay()事件重播。 - 作为临时措施,将
ngSkipHydration属性添加到有问题的元素上。
预渲染
要预渲染静态路由(通常是主页),请配置 angular.json 中的 prerender 选项:
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"prerender": {
"routes": [
"/",
"/about",
"/contact"
]
}
}
}
}
使代码兼容 SSR
- 避免在服务端使用
window、document、localStorage。 - 使用 Angular 的
isPlatformBrowser()和isPlatformServer()函数守卫浏览器特定代码。
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
constructor(@Inject(PLATFORM_ID) private platformId: Object) {
if (isPlatformBrowser(this.platformId)) {
// 浏览器特定的代码
console.log('在浏览器中运行');
}
}
- 或者,使用
afterNextRender或afterRender来延迟浏览器特定的逻辑:
import { afterNextRender } from '@angular/core';
constructor() {
afterNextRender(() => {
// 此代码仅在浏览器中运行
});
}
测试
在 app.component.spec.ts 中,导入 provideClientHydration() 以确保测试环境支持 SSR:
import { provideClientHydration } from '@angular/platform-browser';
beforeEach(async () => {
await TestBed.configureTestingModule({
providers: [provideClientHydration()]
}).compileComponents();
});
故障排除
- 错误:
ERROR TypeError: Cannot read properties of null (reading '...')-- 通常由水合不匹配引起。检查 DOM 差异。 - 错误:
ReferenceError: window is not defined-- 服务端代码中使用了浏览器 API。使用isPlatformBrowser()守卫或afterNextRender。 - 性能问题:考虑使用
withDomEventsReplay()来减少水合期间的事件处理开销。