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

Next.js App Router+i18next:解决服务端传资源到客户端组件报错

问题解决与最佳实践

直接解决当前报错

你遇到的问题根源是动态导入JSON文件时得到的是ES Module对象,而非纯翻译对象。ES模块导入JSON会返回一个包含default属性的模块对象,直接传递这个模块对象给客户端组件(TranslationsProvider)就会触发Next.js的序列化限制报错。

修改代码,提取模块的default属性即可:

const locale = "en";
const namespaces = ["common"];
// 提取JSON的默认导出,得到纯翻译对象
const resources = (await import(`@/locales/${locale}/common.json`)).default;

<TranslationsProvider
  locale={locale}
  namespaces={namespaces}
  // 现在传递的是纯对象,符合序列化要求
  resources={{ [locale]: { common: resources } }}
>
  {children}
</TranslationsProvider>

App Router中i18next传递翻译资源的最佳实践

1. 始终传递纯可序列化对象

服务端组件向客户端组件传递props时,必须确保是纯对象、字符串、数字等可序列化类型,禁止传递Module、类实例等非纯对象。所有动态导入的JSON资源都要提取default属性。

2. 服务端预加载翻译资源

在根layout(服务端组件)中根据当前locale预加载所需的翻译命名空间,再传递给客户端的TranslationsProvider。这样能避免客户端额外请求翻译文件,提升首屏性能。

示例:

// app/[locale]/layout.tsx (服务端组件)
import { getLocaleFromParams } from "@/lib/i18n";
import TranslationsProvider from "@/components/TranslationsProvider";

export default async function RootLayout({
  children,
  params: { locale },
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  const namespaces = ["common", "home"];
  // 批量加载多个命名空间的翻译资源
  const resources = await Promise.all(
    namespaces.map(async (ns) => {
      const translation = (await import(`@/locales/${locale}/${ns}.json`)).default;
      return { [ns]: translation };
    })
  );

  // 整理成i18next需要的resources结构
  const mergedResources = {
    [locale]: Object.assign({}, ...resources),
  };

  return (
    <html lang={locale}>
      <body>
        <TranslationsProvider
          locale={locale}
          namespaces={namespaces}
          resources={mergedResources}
        >
          {children}
        </TranslationsProvider>
      </body>
    </html>
  );
}

3. 客户端Provider初始化i18next

在客户端组件TranslationsProvider中,使用传递的locale、namespaces、resources初始化i18next,避免重复加载资源:

// components/TranslationsProvider.tsx (客户端组件)
"use client";

import { useEffect } from "react";
import { initReactI18next } from "react-i18next";
import i18n from "@/lib/i18n";

type Props = {
  locale: string;
  namespaces: string[];
  resources: Record<string, Record<string, any>>;
  children: React.ReactNode;
};

export default function TranslationsProvider({
  locale,
  namespaces,
  resources,
  children,
}: Props) {
  useEffect(() => {
    // 初始化或更新i18n配置
    if (!i18n.isInitialized) {
      i18n.use(initReactI18next).init({
        lng: locale,
        ns: namespaces,
        resources,
        interpolation: { escapeValue: false },
      });
    } else {
      // 切换语言时更新资源和语言
      i18n.changeLanguage(locale);
      i18n.addResources(locale, namespaces, resources[locale]);
    }
  }, [locale, namespaces, resources]);

  return <>{children}</>;
}

4. 结合Middleware处理路由本地化

确保middleware.ts正确重写路由,提取locale参数,让根layout能拿到正确的locale值,避免硬编码:

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { i18nConfig } from "@/lib/i18nConfig";

export function middleware(request: NextRequest) {
  const pathname = request.nextUrl.pathname;
  const localeMatch = pathname.match(/^\/([a-z]{2})\/?/);
  const locale = localeMatch ? localeMatch[1] : i18nConfig.defaultLocale;

  // 如果路径没有locale前缀,重定向到带locale的路径
  if (!localeMatch && !i18nConfig.locales.includes(locale)) {
    return NextResponse.redirect(
      new URL(`/${i18nConfig.defaultLocale}${pathname}`, request.url)
    );
  }
}

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

5. 避免客户端重复请求

不要在客户端组件中动态导入翻译资源,尽量在服务端预加载后传递给客户端,减少客户端请求次数,提升SSR性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 01:27:07