Angular预渲染场景下如何从服务端侧提供并注入自定义token?
问题根因
你的现有方案在预渲染场景不生效的核心逻辑是:
- 正常SSR请求会走express的路由处理逻辑,
res.render传入的providers优先级高于模块内注册的provider,会覆盖你写的空字符串默认值,所以可以正常工作 - 预渲染阶段不会触发express的请求处理流程,
res.render内的自定义provider逻辑不会执行,应用只能拿到模块和ngExpressEngine中注册的空字符串值
可行实现方案
下面提供两种常用的兼容方案:
方案一:自定义预渲染逻辑注入对应token
如果你使用自定义预渲染脚本,可以在调用Angular服务端渲染能力时,为每个预渲染的路由主动注入对应值:
UNIVERSAL_LOCATION直接拼接当前预渲染路由和你的站点域名即可UNIVERSAL_USER_AGENT可以自定义一个预渲染场景专用的标识值
示例代码如下:
import { renderModule } from '@angular/platform-server'; import { AppServerModule } from './src/main.server'; // 待预渲染的路由列表 const prerenderRoutes = ['/', '/about', '/product/list']; const PRERENDER_UA = 'AngularPrerender/1.0'; const SITE_DOMAIN = 'https://your-official-site.com'; async function runPrerender() { for (const route of prerenderRoutes) { const fullPageUrl = `${SITE_DOMAIN}${route}`; const renderedHtml = await renderModule(AppServerModule, { document: '<app-root></app-root>', // 替换为你实际的index.html模版内容 url: route, // 注入预渲染场景的自定义token extraProviders: [ { provide: UNIVERSAL_LOCATION, useValue: fullPageUrl }, { provide: UNIVERSAL_USER_AGENT, useValue: PRERENDER_UA }, ] }); // 此处添加将renderedHtml写入静态文件的逻辑 } }
如果你用Angular官方的prerender构建器,可以在angular.json的对应构建配置中添加extraProviders参数传入上述两个自定义provider即可。
方案二:编写兼容多场景的通用工厂Provider
不需要修改预渲染脚本,直接改造AppServerModule中的provider注册逻辑,自动适配SSR和预渲染场景:
首先删除你在ngExpressEngine和AppServerModule中注册的固定空值provider,替换为如下实现:
import { NgModule, Inject, Optional, PLATFORM_ID } from '@angular/core'; import { isPlatformServer, DOCUMENT } from '@angular/common'; import { REQUEST } from '@nguniversal/express-engine/tokens'; // UA注入工厂 export function universalUserAgentFactory( @Optional() @Inject(REQUEST) req: any, @Inject(PLATFORM_ID) platformId: Object ): string { // SSR场景有请求对象,直接取请求UA if (isPlatformServer(platformId) && req) { return req.headers['user-agent'] || ''; } // 预渲染场景无请求对象,返回自定义默认UA return 'Prerender/1.0'; } // 地址注入工厂 export function universalLocationFactory( @Optional() @Inject(REQUEST) req: any, @Inject(PLATFORM_ID) platformId: Object, @Inject(DOCUMENT) doc: Document ): string { if (isPlatformServer(platformId) && req) { // SSR场景取请求完整地址 return `${req.protocol}://${req.get('host')}${req.originalUrl}`; } // 预渲染场景直接取渲染上下文的地址 return doc.location.href; } @NgModule({ imports: [AppModule, ServerModule, UniversalModule, FlexLayoutServerModule], bootstrap: [AppComponent], providers: [ { provide: UNIVERSAL_USER_AGENT, useFactory: universalUserAgentFactory, deps: [REQUEST, PLATFORM_ID] }, { provide: UNIVERSAL_LOCATION, useFactory: universalLocationFactory, deps: [REQUEST, PLATFORM_ID, DOCUMENT] }, ], }) export class AppServerModule {}
同时可以删除express路由中res.render里传入的provideLocation和provideUserAgent,工厂函数会自动处理SSR场景的取值。
注意:必须给
REQUEST参数添加@Optional()装饰器,预渲染场景下不存在该token,不加会抛出注入异常。
注意事项
如果你的业务逻辑对UA或者地址有强校验规则,需要保证预渲染场景的默认值符合校验要求,避免预渲染内容和SSR内容不一致导致水合报错。
内容的提问来源于stack exchange,提问作者Thabo
相关产品推荐
相关产品推荐

