You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Angular 20 SSR仅生成单个server.mjs(与Angular 19不同),如何修复本地化SSR渲染问题?

Angular 20 SSR仅生成单个server.mjs(与Angular 19不同),如何修复本地化SSR渲染问题?

我刚踩过这个坑!Angular 20在SSR本地化这块的改动确实挺反直觉的,之前Angular 19那种按locale生成独立server文件的方式没了,默认只出一个全局server.mjs,直接导致服务端渲染全用默认locale,客户端再切的话SEO和首屏体验全崩,尤其你用Firebase Cloud Functions的场景,这个问题更突出。

我折腾了好几天,终于摸清楚了官方的新玩法——现在Angular推荐用单server bundle动态切换locale的方式来做本地化SSR,而不是靠预编译多份server文件。下面是我亲测有效的步骤,适配Firebase Cloud Functions的场景:

1. 先确认angular.json的配置没跑偏

首先得确保你的i18n和本地化配置还在,别因为升级丢了。重点检查这几个地方:

  • projects.architect.build.configurations里保留各个locale的配置(比如en、fr、de),每个配置里加"localize": ["对应locale代码"]
  • projects.architect.server.options里的localize设为true,这样构建server bundle时会把所有需要的locale数据打包进去
  • 构建命令还是用ng build --localize && ng run 你的项目名:server --localize,这样生成的server.mjs会包含所有locale的国际化数据

2. 核心改动:修改SSR请求处理逻辑(适配Firebase)

原来的多server文件是预编译好每个locale的应用,现在要改成请求进来时动态切换locale再渲染。因为你用Firebase Cloud Functions,所以要改函数的入口文件(一般是functions/src/index.ts)或者server.ts:

第一步:导入并注册locale数据

先把你需要的locale数据导入进来,也可以用动态导入优化体积:

import { registerLocaleData } from '@angular/common';
// 预导入常用locale,或者用下面的动态导入方式
import en from '@angular/common/locales/en';
import fr from '@angular/common/locales/fr';
import de from '@angular/common/locales/de';

// 预注册所有需要的locale
registerLocaleData(en);
registerLocaleData(fr);
registerLocaleData(de);

// 或者用动态导入(适合locale多的情况,减小初始bundle)
async function loadAndRegisterLocale(locale: string) {
  switch (locale) {
    case 'fr':
      const frData = await import('@angular/common/locales/fr');
      registerLocaleData(frData.default);
      break;
    case 'de':
      const deData = await import('@angular/common/locales/de');
      registerLocaleData(deData.default);
      break;
    default:
      const enData = await import('@angular/common/locales/en');
      registerLocaleData(enData.default);
  }
}

第二步:在请求中解析locale并动态渲染

在Firebase函数的请求处理逻辑里,先从请求里拿到locale(可以从URL路径、Accept-Language头或者Cookie取,我这里用URL路径的第一个分段,比如/en/about里的en),然后覆盖Angular的LOCALE_ID提供商,再渲染对应内容:

import * as functions from 'firebase-functions';
import { renderApplication } from '@angular/platform-server';
import { AppServerModule } from './src/main.server';
import { readFileSync } from 'fs';
import { join } from 'path';
import { LOCALE_ID } from '@angular/core';

// 读取预生成的index.html模板
const indexHtml = readFileSync(join(__dirname, '../dist/你的项目名/browser/index.html'), 'utf-8');
// 定义支持的locale列表,防止非法值
const supportedLocales = ['en', 'fr', 'de'];

export const ssr = functions.https.onRequest(async (req, res) => {
  // 1. 解析请求的locale(这里用URL路径的第一个分段,你可以换成自己的逻辑)
  const requestedLocale = req.path.split('/')[1] || 'en';
  const validLocale = supportedLocales.includes(requestedLocale) ? requestedLocale : 'en';

  // 2. 注册locale数据(如果用动态导入的话调用上面的loadAndRegisterLocale)
  // await loadAndRegisterLocale(validLocale);

  try {
    // 3. 用自定义提供商覆盖LOCALE_ID,渲染对应locale的页面
    const renderedHtml = await renderApplication(AppServerModule, {
      document: indexHtml,
      url: req.originalUrl,
      extraProviders: [
        { provide: LOCALE_ID, useValue: validLocale }
      ]
    });

    res.status(200).set('Content-Type', 'text/html').send(renderedHtml);
  } catch (err) {
    console.error('SSR渲染出错:', err);
    res.status(500).send('服务器内部错误');
  }
});

3. 部署测试

把修改后的代码重新构建部署到Firebase Cloud Functions,然后测试:

  • 访问https://你的域名/en/xxx,查看页面源码,确认本地化文本、日期/货币格式都是英文的
  • 访问https://你的域名/fr/xxx,同理确认是法文内容
  • 重点看首屏渲染的HTML,确保内容是对应locale的,而不是默认locale再客户端切换

为什么Angular 20要改这个?

官方说主要是为了减少构建产物数量、优化构建时间——之前每个locale生成一份server文件,项目大了的话构建慢到离谱,产物也占空间。现在用单bundle动态切换的方式,构建速度和产物体积都能优化不少,只是需要我们改一下SSR的处理逻辑。

踩坑提醒

  • 如果你用了第三方库的国际化,要确保它们也支持动态切换locale,而不是依赖预编译的LOCALE_ID
  • 动态导入locale数据的时候,要注意Firebase的冷启动时间,常用locale可以预导入,冷门的再动态加载
  • 一定要验证requestedLocale是否在支持列表里,避免非法值导致渲染出错

内容来源于stack exchange

火山引擎 最新活动