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

相关配置文件
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" ] }
排查与解决步骤
修复tsconfig.server.json语法错误
该文件的types数组里有多余的逗号,删除后可避免JSON解析异常:"types": [ "node" ]统一路径别名配置
由于tsconfig.server.json继承了tsconfig.app.json的路径别名,需确认thkee-common这类本地库的路径在服务端编译时可正常访问。可在tsconfig.server.json中显式重复声明所有路径别名,消除继承带来的路径解析问题。升级不兼容依赖
部分依赖(如angular-svg-icon@14.0.0)版本停留在Angular 14,可能与Angular 17 SSR环境冲突,需升级到适配Angular 17的版本。同时检查@angular/fire的SSR初始化配置,确保服务端环境下不会调用浏览器专属API。调整服务端TypeScript模块配置
Angular 17 SSR推荐服务端编译使用CommonJS模块格式,修改tsconfig.server.json的module字段:"module": "CommonJS"确保
moduleResolution保持为node,提升Node.js环境兼容性。处理服务端环境兼容性
在自动生成的server.ts中,添加平台环境判断逻辑,避免服务端代码调用浏览器全局对象(如window、document):import { isPlatformServer } from '@angular/common'; import { PLATFORM_ID } from '@angular/core'; // 在初始化逻辑中添加判断 if (isPlatformServer(PLATFORM_ID)) { // 服务端专属逻辑或兼容处理 }清理缓存后重新构建
执行以下命令彻底清理缓存并重新构建SSR:rm -rf node_modules dist npm install npm run build:ssr npm run serve:ssr
内容的提问来源于stack exchange,提问作者Al Imran

