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

