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

Angular 17 SSR本地Node.js启动报错问题求助

Angular 14 迁移至 Angular 17 后 SSR 启动失败

近期将应用从 Angular 14 迁移到 Angular 17,并尝试启用 SSR 功能。SSR 构建时浏览器端和服务端编译均正常,但通过 ng add @angular/ssr 自动生成的 Node.js 应用启动 SSR 时出现错误。

错误截图:
Angular 17 SSR 错误1
Angular 17 SSR 错误2

相关配置文件

package.json

{
  "name": "my-app",
  "version": "0.9.1",
  "private": true,
  "scripts": {
    "ng": "ng",
     ....
     ....
    "dev:ssr": "NODE_OPTIONS=--max_old_space_size=4096 ng run my-app:serve-ssr",
    "serve:ssr": "node dist/my-app/server/main.js",
    "build:ssr": "ng build my-app && ng run my-app:server",
    "prerender": "NODE_OPTIONS=--max_old_space_size=4096 ng run my-app:prerender"
  },
  "dependencies": {
    "@angular/animations": "~17.3.12",
    "@angular/cdk": "^17.3.10",
    "@angular/common": "~17.3.12",
    "@angular/compiler": "~17.3.12",
    "@angular/core": "~17.3.12",
    "@angular/fire": "^17.1.0",
    "@angular/forms": "~17.3.12",
    "@angular/localize": "~17.3.12",
    "@angular/platform-browser": "~17.3.12",
    "@angular/platform-browser-dynamic": "~17.3.12",
    "@angular/platform-server": "~17.3.12",
    "@angular/router": "~17.3.12",
    "@angular/service-worker": "~17.3.12",
    "@angular/ssr": "^17.3.11",
    "@bartholomej/ngx-translate-extract": "^8.0.2",
    "@faker-js/faker": "^8.2.0",
    "@fortawesome/fontawesome-free": "^6.1.2",
    "@gloriousky/tailwindcss-localization": "^1.1.0",
    "@milkdown/core": "^7.5.0",
    "@milkdown/ctx": "^7.5.0",
    "@milkdown/kit": "^7.5.5",
    "@milkdown/plugin-history": "^7.5.0",
    "@milkdown/plugin-listener": "^7.5.0",
    "@milkdown/plugin-prism": "^7.5.0",
    "@milkdown/plugin-tooltip": "^7.3.6",
    "@milkdown/plugin-upload": "^7.5.0",
    "@milkdown/preset-commonmark": "^7.5.0",
    "@milkdown/prose": "^7.5.0",
    "@milkdown/theme-nord": "^7.5.0",
    "@milkdown/transformer": "^7.5.0",
    "@ngrx/effects": "^17.2.0",
    "@ngrx/entity": "^17.2.0",
    "@ngrx/router-store": "^17.2.0",
    "@ngrx/store": "^17.2.0",
    "@ngrx/store-devtools": "^17.2.0",
    "@ngx-formly/bootstrap": "^6.1.8",
    "@ngx-formly/core": "^6.1.8",
    "@ngx-translate/core": "^14.0.0",
    "@popperjs/core": "^2.11.0",
    "@types/video.js": "^7.3.53",
    "@uppy/angular": "^0.5.2",
    "@uppy/aws-s3": "^3.2.3",
    "@uppy/aws-s3-multipart": "^3.5.4",
    "@uppy/golden-retriever": "^3.1.0",
    "@uppy/image-editor": "^2.1.2",
    "@uppy/remote-sources": "^1.0.3",
    "angular-svg-icon": "^14.0.0",
    "animate.css": "^4.1.1",
    "chartjs-plugin-annotation": "^3.0.1",
    "chartjs-plugin-datalabels": "^2.2.0",
    "conditio": "^2.3.4",
    "express": "^4.18.2",
    "flowbite": "^2.5.1",
    "flowbite-datepicker": "^1.3.0",
    "i": "^0.3.7",
    "js-cookie": "^3.0.5",
    "js-sha256": "^0.11.0",
    "lodash": "^4.17.20",
    "moment": "^2.29.4",
    "ng-circle-progress": "^1.7.1",
    "ng-milkdown": "^0.0.3",
    "ng-prosemirror-adapter": "^0.0.6",
    "ng2-charts": "^4.1.1",
    "ngx-markdown": "^17.2.1",
    "npm": "^9.8.1",
    "prism-themes": "^1.9.0",
    "reconnecting-websocket": "^4.4.0",
    "remark-directive": "^3.0.0",
    "rxjs": "^7.5.0",
    "swiper": "^11.1.14",
    "tslib": "^2.3.0",
    "uuid": "^9.0.0",
    "video.js": "^8.5.2",
    "zone.js": "~0.14.10"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~17.3.9",
    "@angular-eslint/builder": "~17.5.3",
    "@angular-eslint/eslint-plugin": "~17.5.3",
    "@angular-eslint/eslint-plugin-template": "~17.5.3",
    "@angular-eslint/schematics": "~17.5.3",
    "@angular-eslint/template-parser": "~17.5.3",
    "@angular/cli": "~17.3.9",
    "@angular/compiler-cli": "~17.3.12",
    "@angular/language-service": "~17.3.12",
    "@biesbjerg/ngx-translate-extract": "^7.0.3",
    "@biesbjerg/ngx-translate-extract-marker": "^1.0.0",
    "@cypress/code-coverage": "^3.12.39",
    "@cypress/schematic": "^2.0.3",
    "@ngneat/until-destroy": "^9.0.0",
    "@ngx-rocket/scripts": "^5.2.2",
    "@types/express": "^4.17.17",
    "@types/jasmine": "^4.0.0",
    "@types/jasminewd2": "^2.0.8",
    "@types/js-cookie": "^3.0.6",
    "@types/lodash": "^4.14.184",
    "@types/node": "^18.13.0",
    "@types/uuid": "^9.0.2",
    "@typescript-eslint/eslint-plugin": "^7.2.0",
    "@typescript-eslint/parser": "^7.2.0",
    "autoprefixer": "^10.4.14",
    "browser-sync": "^3.0.0",
    "cypress": "~10.6.0",
    "eslint": "^8.57.0",
    "eslint-plugin-import": "latest",
    "eslint-plugin-jsdoc": "latest",
    "eslint-plugin-prefer-arrow": "latest",
    "fs-extra": "^11.2.0",
    "https-proxy-agent": "^5.0.0",
    "husky": "^8.0.1",
    "istanbul-lib-coverage": "^3.2.2",
    "jasmine-core": "~4.2.0",
    "jasmine-spec-reporter": "~7.0.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.0.0",
    "karma-junit-reporter": "^2.0.1",
    "ng-packagr": "^17.3.0",
    "nyc": "^15.1.0",
    "postcss": "^8.4.27",
    "prettier": "^2.8.8",
    "prettier-plugin-tailwindcss": "^0.4.1",
    "pretty-quick": "^3.1.0",
    "stylelint": "~14.11.0",
    "stylelint-config-prettier": "^9.0.3",
    "stylelint-config-recommended-scss": "~7.0.0",
    "stylelint-config-standard": "~28.0.0",
    "tailwindcss": "^3.3.3",
    "ts-node": "^10.1.0",
    "typescript": "~5.4.5"
  },
  "prettier": {
    "tabWidth": 2,
    "singleQuote": true,
    "overrides": [
      {
        "files": "*.scss",
        "options": {
          "singleQuote": false
        }
      }
    ]
  }
}

tsconfig.app.json

/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "../../out-tsc/app",
    "types": [],
    "paths": {
      "@shared": ["./projects/my-app/src/app/@shared"],
      "@shared/*": ["./projects/my-app/src/app/@shared/*"],
      "@app": ["./projects/my-app/src/app"],
      "@app/*": ["./projects/my-app/src/app/*"],
      "@env/*": ["./projects/my-app/src/environments/*"],
      "thkee-common": [
        /** Point to direct source to include thkee-common source in app build */
        "./projects/thkee-common/src/public-api"
      ]
    }
  },
  // "files": ["src/main.ts", "src/polyfills.ts"],
  "include": ["src/**/*.ts"],
  "exclude": ["src/test.ts", "src/**/*.spec.ts", "src/**/*.mock.ts"]
}

tsconfig.json

{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "module": "ES2022",
    "moduleResolution": "node",
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "importHelpers": true,
    "strict": true,
    // "suppressImplicitAnyIndexErrors": true,
    "target": "ES2022",
    "allowSyntheticDefaultImports":true,
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "ES2022",
      "dom"
    ],
    "baseUrl": "./",
    "types": [
      "node"
    ],
    "useDefineForClassFields": false
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "fullTemplateTypeCheck": true,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true,
    "preserveWhitespaces": true
  },
  "files": [],
  "references": [
    {
      "path": "./projects/my-app/tsconfig.app.json"
    },
    {
      "path": "./projects/my-app/tsconfig.spec.json"
    },
    {
      "path": "./projects/my-app/tsconfig.app.json"
    },
    {
      "path": "./projects/my-app/tsconfig.spec.json"
    },
    {
      "path": "./projects/my-common/tsconfig.lib.json"
    },
    {
      "path": "./projects/my-common/tsconfig.spec.json"
    }
  ]
}

tsconfig.server.json

/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
  "extends": "./tsconfig.app.json",
  "compilerOptions": {
    "outDir": "../../out-tsc/server",
    "target": "ES2022",
    "module": "ES2022",
    "types": [
      "node",
    ],
    "lib": ["ES2022", "dom"],
    "useDefineForClassFields": false
  },
  "files": [
    "src/main.ts",
    "src/main.server.ts",
    "server.ts"
  ],
  "include": [
    "src/**/*.d.ts"
  ]
}

排查与解决步骤

  1. 修复tsconfig.server.json语法错误
    该文件的types数组里有多余的逗号,删除后可避免JSON解析异常:

    "types": [
      "node"
    ]
    
  2. 统一路径别名配置
    由于tsconfig.server.json继承了tsconfig.app.json的路径别名,需确认thkee-common这类本地库的路径在服务端编译时可正常访问。可在tsconfig.server.json中显式重复声明所有路径别名,消除继承带来的路径解析问题。

  3. 升级不兼容依赖
    部分依赖(如angular-svg-icon@14.0.0)版本停留在Angular 14,可能与Angular 17 SSR环境冲突,需升级到适配Angular 17的版本。同时检查@angular/fire的SSR初始化配置,确保服务端环境下不会调用浏览器专属API。

  4. 调整服务端TypeScript模块配置
    Angular 17 SSR推荐服务端编译使用CommonJS模块格式,修改tsconfig.server.json的module字段:

    "module": "CommonJS"
    

    确保moduleResolution保持为node,提升Node.js环境兼容性。

  5. 处理服务端环境兼容性
    在自动生成的server.ts中,添加平台环境判断逻辑,避免服务端代码调用浏览器全局对象(如window、document):

    import { isPlatformServer } from '@angular/common';
    import { PLATFORM_ID } from '@angular/core';
    
    // 在初始化逻辑中添加判断
    if (isPlatformServer(PLATFORM_ID)) {
      // 服务端专属逻辑或兼容处理
    }
    
  6. 清理缓存后重新构建
    执行以下命令彻底清理缓存并重新构建SSR:

    rm -rf node_modules dist
    npm install
    npm run build:ssr
    npm run serve:ssr
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 05:04:52