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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 09:36:00