引言
虽然 Universal 项目的目标是在服务器端无缝渲染 Angular 应用,但开发过程中存在几个“隐藏规则”值得提前熟悉。服务器与浏览器环境本质上截然不同——在服务端渲染时,应用仅处于一种“快照”状态:完整呈现一次,返回生成的 HTML,随后状态被清空,等待下次请求重新开始。这种一次性渲染机制迫使开发者必须放弃某些“浏览器原生”的依赖。
服务器环境既没有 cookie,也不具备 DOM 和 BOM 等全局对象。即便借助 polyfill 可以弥补部分缺陷,但始终无法达到完美。更重要的是,需时刻铭记 SSR 的核心目标是提升首屏加载速度,因此任何可能拖慢初始渲染的操作都应当避免或提前做好防护。下面就来讨论最常见的几个“翻车现场”。
window is not defined
这是使用 Angular Universal 时出现频率最高的报错之一,原因很简单:服务器上的 DOM 是通过 domino 这个库模拟的,并非真实的浏览器环境。因此,window、document、cookie、canvas 等浏览器专属的全局变量均不存在或不可用。Domino 只是“代表”了 DOM,而非“成为”DOM。

解决这个错误的一些思路
一个非常实用的做法是:避免直接访问全局变量,而是利用 Angular 的依赖注入(DI)系统来获取它们。例如,DOCUMENT 令牌可以安全地拿到 Document 对象,再通过它获取 window 和 location 的原始版本。代码示例如下:
import { Injectable, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';
@Injectable()
export class ExampleService {
constructor(@Inject(DOCUMENT) private _doc: Document) {}
getWindow(): Window | null {
return this._doc.defaultView;
}
getLocation(): Location {
return this._doc.location;
}
createElement(tag: string): HTMLElement {
return this._doc.createElement(tag);
}
}
这样既保证了代码在服务端也能正常运行,又保持了清晰的依赖关系,算是一条较为稳妥的路径。当然,实际场景中可能还会遇到其他类似问题,但核心逻辑都是一样的——先摸清环境差异,再针对性地选择安全的替代方案。
