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

Angular14升级Firebase9后SSR报id未定义错误崩溃

问题概述
  • 完成Angular v13到v14、Firebase v8到v9模块化版本升级后,本地测试全流程正常,服务器部署后服务运行约6小时触发id未定义报错,直接导致SSR服务崩溃,必须执行npm run serve:ssr才能重启恢复。
  • 同类报错在Angular v13阶段曾出现但从未引发崩溃,升级v14后崩溃频率大幅提升。

报错信息截图

当前server.ts配置

(global as any).WebSocket = require('ws');
(global as any).XMLHttpRequest = require('xhr2');
import 'zone.js/dist/zone-node';

const domino = require('domino');
const fs = require('fs');
const path = require('path');


import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { existsSync } from 'fs';

const distFolder = join(process.cwd(), 'dist/projectfolder/browser');
const template = fs.readFileSync(path.join(distFolder, 'index.html')).toString();
const win = domino.createWindow(template.toString());
global['window'] = win;
global['document'] = win.document;
global['DOMTokenList'] = win.DOMTokenList;
global['Node'] = win.Node;
global['Text'] = win.Text;
global['HTMLElement'] = win.HTMLElement;
global['navigator'] = win.navigator;
global['getComputedStyle'] = win.getComputedStyle;

(global as any).self = {fetch: require('node-fetch')};



import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';
import { environment } from './src/environments/environment';

// Express实例可导出供serverless函数使用
export function app(): express.Express {
  const server = express();
  const distFolder = join(process.cwd(), 'dist/projectfolder/browser');
  const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';

  // Universal express-engine 配置
  server.engine('html', ngExpressEngine({
    bootstrap: AppServerModule,
  }));

  server.set('view engine', 'html');
  server.set('views', distFolder);

  // 静态资源服务配置
  server.get('*.*', express.static(distFolder, {
    maxAge: '1y'
  }));

  const routesPath = ['/invite', '/invite/**','/lm', '/dashboard', '/dashboard/**', '/public/**', '/public', '/pre'];
  server.get(routesPath, (req, res) => {
    res.sendFile(distFolder + '/index.html');
  });
  
  // 其余路由走SSR渲染
  server.get('*', (req, res) => {
    res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
  });

  return server;
}

function run(): void {
  const port = process.env.PORT || environment.PORT;
  const server = app();
  server.listen(port, () => {
    console.log(`Node Express server listening on https://localhost:${port}`);
  });
}

// 适配webpack打包逻辑,确保直接运行时启动服务
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
  run();
}

export * from './src/main.server';
故障原因

三个问题叠加导致该故障:

  • 全局单例DOM污染:当前用domino创建的window、document等对象直接挂在global上是所有请求共享的单例,Angular 14对渲染上下文的校验比v13更严格,长时间运行多请求并发时,异步回调(尤其是Firebase的网络请求回调)会拿到已被前序请求回收的DOM节点引用,访问id属性时直接抛出未定义错误;Angular v13会自动降级吞掉这类渲染异常,v14会将异常抛到进程顶层直接触发退出,因此崩溃频率明显上升。
  • Firebase v9 polyfill缺失:Firebase v9模块化版本运行在SSR环境时,会从全局self对象上读取crypto、structuredClone等API,当前配置把self覆盖为仅含fetch的对象,Firebase内部鉴权、Firestore模块的缓存上下文长时间运行失效后,访问内部实例id属性就会报错。
  • 无异常兜底逻辑:当前服务既没有进程级的未捕获异常监听,也没有Express渲染层的错误回调,偶发异常会直接导致Node进程退出。
修复步骤

按以下顺序调整配置即可解决问题:

  1. 补全全局polyfill,修改文件顶部的polyfill逻辑,替换原有self赋值的代码:
(global as any).WebSocket = require('ws');
(global as any).XMLHttpRequest = require('xhr2');
(global as any).crypto = require('crypto').webcrypto;
(global as any).structuredClone = (obj: unknown) => JSON.parse(JSON.stringify(obj));
(global as any).fetch = require('node-fetch');
(global as any).self = global;
import 'zone.js/dist/zone-node';
  1. 移除顶部直接给global赋值window、document等dom属性的逻辑,改为在ngExpressEngine中为每个请求创建独立的dom上下文,修改engine配置:
const distFolder = join(process.cwd(), 'dist/projectfolder/browser');
const template = fs.readFileSync(path.join(distFolder, 'index.html')).toString();

server.engine('html', (filePath, options, callback) => {
  // 每个请求创建独立的dom上下文,避免状态污染
  const win = domino.createWindow(template);
  const providers = [
    { provide: APP_BASE_HREF, useValue: options.req.baseUrl },
    { provide: 'WINDOW', useValue: win },
    { provide: 'DOCUMENT', useValue: win.document }
  ];
  ngExpressEngine({
    bootstrap: AppServerModule,
    providers
  })(filePath, options, callback);
});
  1. 添加进程级异常捕获,在run()函数调用前添加以下代码,避免偶发异常直接退出进程:
process.on('uncaughtException', (err) => {
  console.error('未捕获异常:', err.message, err.stack);
});
process.on('unhandledRejection', (reason) => {
  console.error('未处理Promise异常:', reason);
});
  1. 给SSR渲染逻辑加错误兜底,修改通用路由配置,渲染失败时直接返回静态CSR版本的index.html:
server.get('*', (req, res) => {
  res.render(indexHtml, {
    req,
    providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }]
  }, (err, html) => {
    if (err) {
      console.error('SSR渲染异常:', err.message);
      res.sendFile(join(distFolder, 'index.html'));
      return;
    }
    res.send(html);
  });
});
  1. 可选高可用配置:使用pm2托管SSR进程,配置异常自动重启,启动命令替换为pm2 start npm --name "ssr-service" -- run serve:ssr,避免极端场景下服务不可用。

内容的提问来源于stack exchange,提问作者Santosh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 00:57:20