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
相关产品推荐
相关产品推荐

