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

Angular 14升级后SSR模式下Primeng等样式加载失败问题

Angular 14 SSR升级后的样式加载与预渲染问题

将项目从Angular 13升级到Angular 14后,遇到两个SSR相关问题:

  • 服务器端渲染时仅style.scss生效,theme.css及PrimeNG等第三方组件样式无法加载,但非SSR模式(执行npm run start)下样式完全正常。
  • 执行npm run prerender时,出现错误提示:Unable to extract routes from application。

项目配置文件信息

angular.json

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "my-app": {
      "i18n": {
        "sourceLocale": "fr-FR"
      },
      "projectType": "application",
      "schematics": {
        "@schematics/angular:component": {
          "style": "scss"
        },
        "@schematics/angular:application": {
          "strict": true
        }
      },
      "root": "",
      "sourceRoot": "src",
      "prefix": "fp",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/my-app/browser",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "inlineStyleLanguage": "scss",
            "assets": [
              "src/favicon.ico",
              "src/assets",
              {
                "glob": "**/*",
                "input": "node_modules/tinymce",
                "output": "/tinymce/"
              },
              {
                "glob": "**/*",
                "input": "node_modules/ngx-extended-pdf-viewer/assets/",
                "output": "/assets/"
              }
            ],
            "styles": [
              "src/styles.scss",
              "src/theme.css",
              "node_modules/primeng/resources/primeng.min.css",
              "node_modules/primeflex/primeflex.min.css",
              "node_modules/primeicons/primeicons.css"
            ],
            "stylePreprocessorOptions": {
              "includePaths": [
                "src/styles"
              ]
            },
            "scripts": [
              "node_modules/tinymce/tinymce.min.js"
            ]
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kb",
                  "maximumError": "4kb"
                }
              ],
              "outputHashing": "all"
            },
            "development": {
              "buildOptimizer": false,
              "optimization": false,
              "vendorChunk": true,
              "extractLicenses": false,
              "sourceMap": true,
              "namedChunks": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "configurations": {
            "production": {
              "browserTarget": "my-app:build:production",
              "proxyConfig": "src/proxy.conf.ts"
            },
            "development": {
              "browserTarget": "my-app:build:development",
              "proxyConfig": "src/proxy.conf.ts"
            }
          },
          "defaultConfiguration": "development"
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "my-app:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "inlineStyleLanguage": "scss",
            "assets": [
              "src/favicon.ico",
              "src/assets",
              {
                "glob": "**/*",
                "input": "node_modules/tinymce",
                "output": "/tinymce/"
              }
            ],
            "styles": [
              "src/styles.scss",
              "src/theme.css",
              "node_modules/primeng/resources/primeng.min.css",
              "node_modules/primeflex/primeflex.min.css",
              "node_modules/primeicons/primeicons.css"
            ],
            "scripts": [
              "node_modules/tinymce/tinymce.min.js"
            ]
          }
        },
        "lint": {
          "builder": "@angular-eslint/builder:lint",
          "options": {
            "lintFilePatterns": [
              "src/**/*.ts",
              "src/**/*.html"
            ]
          }
        },
        "server": {
          "builder": "@angular-devkit/build-angular:server",
          "options": {
            "outputPath": "dist/my-app/server",
            "main": "server.ts",
            "tsConfig": "tsconfig.server.json",
            "stylePreprocessorOptions": {
              "includePaths": [
                "src/styles"
              ]
            },
            "inlineStyleLanguage": "scss"
          },
          "configurations": {
            "production": {
              "outputHashing": "media"
            },
            "development": {
              "optimization": false,
              "sourceMap": true,
              "extractLicenses": false
            }
          },
          "defaultConfiguration": "production"
        },
        "serve-ssr": {
          "builder": "@nguniversal/builders:ssr-dev-server",
          "configurations": {
            "development": {
              "browserTarget": "my-app:build:development",
              "serverTarget": "my-app:server:development"
            },
            "production": {
              "browserTarget": "my-app:build:production",
              "serverTarget": "my-app:server:production"
            }
          },
          "defaultConfiguration": "development"
        },
        "prerender": {
          "builder": "@nguniversal/builders:prerender",
          "options": {
            "routes": [
              "/"
            ]
          },
          "configurations": {
            "production": {
              "browserTarget": "my-app:build:production",
              "serverTarget": "my-app:server:production"
            },
            "development": {
              "browserTarget": "my-app:build:development",
              "serverTarget": "my-app:server:development"
            }
          },
          "defaultConfiguration": "production"
        }
      }
    }
  },
  "cli": {
    "schematicCollections": [
      "@angular-eslint/schematics"
    ]
  }
}

package.json

"dependencies": {
    "@angular/animations": "^14.2.2",
    "@angular/cdk": "^14.2.2",
    "@angular/common": "^14.2.2",
    "@angular/compiler": "^14.2.2",
    "@angular/core": "^14.2.2",
    "@angular/forms": "^14.2.2",
    "@angular/platform-browser": "^14.2.2",
    "@angular/platform-browser-dynamic": "^14.2.2",
    "@angular/router": "^14.2.2",
    "@fortawesome/angular-fontawesome": "^0.10.1",
    "@fortawesome/fontawesome-svg-core": "^6.1.0",
    "@fortawesome/free-solid-svg-icons": "^6.0.0",
    "@nguniversal/builders": "^14.2.3",
    "@nguniversal/common": "^14.2.3",
    "@nguniversal/express-engine": "^14.2.3",
    "@ngx-translate/core": "^14.0.0",
    "@ngx-translate/http-loader": "^7.0.0",
    "@tinymce/tinymce-angular": "^7.0.0",
    "file-saver": "^2.0.5",
    "jwt-decode": "^3.1.2",
    "karma-viewport": "^1.0.9",
    "moment": "^2.29.1",
    "ngx-colors": "3.1.4",
    "ngx-extended-pdf-viewer": "^15.0.5",
    "primeflex": "^3.1.3",
    "primeng": "^14.1.2",
    "rxjs": "~7.5.0",
    "serializr": "^2.0.5",
    "tinymce": "^6.0.2",
    "tslib": "^2.3.0",
    "zone.js": "~0.11.4"
  },

server.ts

import 'zone.js/dist/zone-node';

const domino = require('domino');
const fs = require('fs');
const path = require('path');
const templateA = fs
  .readFileSync(path.join('dist/my-app/browser', 'index.html'))
  .toString();
const win = domino.createWindow(templateA);
win.Object = Object;
win.Math = Math;

global['window'] = win;
global['document'] = win.document;
global['navigator'] = win.navigator;

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

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

// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
  const server = express();
  const distFolder = join(process.cwd(), 'dist/my-app/browser');
  const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';

  // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
  server.engine('html', ngExpressEngine({
    bootstrap: AppServerModule
  }));

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

  // Example Express Rest API endpoints
  // server.get('/api/**', (req, res) => { });
  // Serve static files from /browser
  server.get('*.*', express.static(distFolder, {
    maxAge: '1y'
  }));

  // All regular routes use the Universal engine
  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'] || 4200;

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

// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
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';

已尝试的操作

已移除原Angular 13的SSR并重新安装,修改过angular.json中的部分配置,但未找到问题根源。

已验证的解决方案

  • 在tsconfig.server.ts的compilerOptions中添加:
"module": "CommonJS"
  • 在server.ts的ngExpressEngine配置中添加inlineCriticalCss: false:
server.engine('html', ngExpressEngine({
  bootstrap: AppServerModule,
  inlineCriticalCss: false,
}));
  • 在angular.json的build.configurations.production中添加优化配置:
"optimization": {
  "scripts": true,
  "styles": {
    "minify": true,
    "inlineCritical": false
  },
  "fonts": false
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 01:01:18