You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular SSR应用中如何安全使用window、document、location等浏览器专属对象

Angular SSR应用中如何安全使用window、document、location等浏览器专属对象

我完全懂你现在的困扰——用Angular 18.2.12搭了SSR应用,构建和启动都没问题,但一用到window、localStorage这些浏览器独有的对象就报错,试了Domino、JSDOM也没搞定,而且项目里这些对象用得还挺多,改起来头大对吧?

下面给你几个实用的解决方案,都是Angular SSR场景下的最佳实践,不用依赖第三方库或者复杂配置:

1. 用Angular官方的平台检测(最推荐)

Angular本身提供了PLATFORM_ID令牌和isPlatformBrowser工具函数,可以帮你在代码里判断当前是运行在浏览器还是服务器端,只有在浏览器环境下才调用这些专属对象。

组件里直接使用的例子:

import { Component, Inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html'
})
export class ExampleComponent {
  constructor(@Inject(PLATFORM_ID) private platformId: Object) {}

  ngOnInit() {
    // 只有在浏览器环境下才访问localStorage
    if (isPlatformBrowser(this.platformId)) {
      const userData = localStorage.getItem('user');
      console.log(userData);
      
      // 操作window对象
      window.addEventListener('resize', () => {
        console.log('窗口大小变化了');
      });
    }
  }
}

封装成全局服务(更适合大量使用的场景)

如果很多组件都要用到,可以封装一个服务来统一处理,避免重复代码:

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

@Injectable({ providedIn: 'root' })
export class BrowserService {
  private isBrowser: boolean;

  constructor(@Inject(PLATFORM_ID) private platformId: Object) {
    this.isBrowser = isPlatformBrowser(this.platformId);
  }

  // 封装localStorage操作
  getLocalStorageItem(key: string): string | null {
    return this.isBrowser ? localStorage.getItem(key) : null;
  }

  setLocalStorageItem(key: string, value: string): void {
    if (this.isBrowser) {
      localStorage.setItem(key, value);
    }
  }

  // 封装window对象的访问
  getWindow(): Window | undefined {
    return this.isBrowser ? window : undefined;
  }

  // 同理可以封装document、location等
  getDocument(): Document | undefined {
    return this.isBrowser ? document : undefined;
  }
}

之后在组件里注入这个服务就行,不用每次都写平台检测的判断。

2. 服务器端模拟浏览器对象(兜底方案)

如果你的项目里这些对象用得太零散,改起来成本太高,可以尝试在server.ts里正确配置JSDOM来模拟浏览器环境——你之前的代码注释了这部分,可能是配置不对导致没生效。

修改你的server.ts,把JSDOM的配置放在最前面,确保Angular模块加载前就模拟好全局对象:

import 'zone.js/node';
import { renderModule } from '@angular/platform-server';
import express from 'express';
import { join } from 'path';
import AppServerModule from './src/main.server';
import { readFileSync } from 'fs';
import { JSDOM } from 'jsdom';

// 第一步:先创建JSDOM实例,模拟浏览器环境
const DIST_FOLDER = join(process.cwd(), 'dist/angular-ssr-project/browser');
const template = readFileSync(join(DIST_FOLDER, 'index.html')).toString();
const dom = new JSDOM(template);

// 把模拟的window、document挂载到全局
global['window'] = dom.window;
global['document'] = dom.window.document;
global['navigator'] = dom.window.navigator;
global['localStorage'] = dom.window.localStorage;
global['sessionStorage'] = dom.window.sessionStorage;
global['location'] = dom.window.location;

const app = express();
const PORT = process.env['PORT'] || 4000;

// Serve static files
app.use(express.static(DIST_FOLDER, {
  maxAge: '1y',
  index: false
}));

// Serve SSR page
app.get('*', (req, res) => {
  // 注意这里要用读取的template作为document,而不是硬写的'<app-root></app-root>'
  renderModule(AppServerModule, {
    document: template,
    url: req.url
  }).then(html => {
    res.send(html);
  }).catch(err => {
    console.error('SSR Rendering Error:', err);
    res.status(500).send('Error during SSR rendering');
  });
});

// Start the server
app.listen(PORT, () => {
  console.log(`Node Express server listening on http://localhost:${PORT}`);
});

注意两点:一是要先加载JSDOM再导入Angular的模块,二是renderModule里的document要使用读取的index.html模板,而不是硬编码的字符串,这样模拟的DOM才和浏览器端一致。

3. 避免在服务器端执行的生命周期钩子中调用浏览器对象

Angular SSR会在服务器端执行组件的constructor、ngOnInit等钩子,所以不要在这些地方直接调用浏览器对象。如果必须要初始化一些依赖浏览器的逻辑,可以放在ngAfterViewInit(这个钩子只在浏览器端执行)里,或者用平台检测包裹。

比如:

ngAfterViewInit() {
  // 这里可以安全访问浏览器对象
  const height = window.innerHeight;
  console.log('窗口高度:', height);
}

额外提醒:处理localStorage的持久化问题

服务器端模拟的localStorage是内存级别的,每次重启服务器就会清空。如果你的业务依赖localStorage的持久化,最好在浏览器端再同步一次数据,或者在服务器端用数据库来存储对应的信息。

备注:内容来源于stack exchange,提问作者Kishan Bhola

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 15:59:49