无需升级next-i18next,解决layout.tsx下react-i18next水化错误
切换语言后刷新/跳转页面时触发react-i18next水化错误,错误提示:
Hydration failed because the server rendered text didn't match the client.
As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:
官方推荐的next-i18next升级方案不适用,因为项目采用layout.tsx架构(App Router),appWithTranslation HOC与该架构不兼容(Server Components不支持此类HOC),且项目的App组件定义为:
const App = (props: {page: string}) => { ... }
而非Pages Router的传统格式:
const App = ({ Component, pageProps }: AppProps) => { ... }
导致使用appWithTranslation会报错,需要无需升级至next-i18next的解决办法。
现有相关代码:
initTranslations钩子
export default async function initTranslations( locale: any, namespaces: string[], i18nInstance?: any, ) { i18nInstance = i18nInstance || createInstance(); i18nInstance.use(initReactI18next); i18nInstance.use(LanguageDetector); await i18nInstance.init({ lng: locale, detection: options, resources, fallbackLng: i18nConfig.defaultLocale, supportedLngs: i18nConfig.locales, defaultNS: namespaces[0], fallbackNS: namespaces[0], ns: namespaces, preload: resources ? [] : i18nConfig.locales }); return { i18n: i18nInstance, resources: i18nInstance.services.resourceStore.data, t: i18nInstance.t }; }
TranslationsProvider组件
export default function TranslationsProvider({ children, locale, namespaces, }: Readonly<{ children: React.ReactNode; locale: string; namespaces: string[]; }>) { const i18n = createInstance(); initTranslations(locale, namespaces, i18n); return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>; }
首页使用示例
export default function Home() { const params = useParams<{ locale: string}>(); return ( <div> <TranslationsProvider namespaces={i18nNamespaces} locale={params.locale}> <App page="" /> </TranslationsProvider> </div> ); }
水化错误的核心是服务端与客户端渲染的翻译文本不一致,以下是针对性修复方案:
1. 异步初始化TranslationsProvider并添加Suspense包裹
当前TranslationsProvider未等待initTranslations异步完成就渲染子组件,导致服务端与客户端初始渲染的内容不匹配。修改为异步组件并通过Suspense处理加载状态:
import { Suspense } from 'react'; import { I18nextProvider } from 'react-i18next'; import createInstance from 'i18next'; import initTranslations from './initTranslations'; // 异步初始化i18n的内部组件 async function AsyncTranslationsProvider({ children, locale, namespaces, }: { children: React.ReactNode; locale: string; namespaces: string[]; }) { const i18n = createInstance(); // 等待初始化完成,确保服务端渲染时已加载对应语言资源 await initTranslations(locale, namespaces, i18n); return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>; } // 对外暴露带Suspense的Provider export default function TranslationsProvider({ children, locale, namespaces, }: { children: React.ReactNode; locale: string; namespaces: string[]; }) { return ( <Suspense fallback={<div>Loading...</div>}> <AsyncTranslationsProvider locale={locale} namespaces={namespaces}> {children} </AsyncTranslationsProvider> </Suspense> ); }
2. 仅在客户端启用LanguageDetector
服务端渲染时,LanguageDetector无法读取客户端存储(如cookie、localStorage),可能导致服务端使用fallback语言,客户端切换为目标语言引发不匹配。修改initTranslations:
// 新增工具函数:判断是否为服务端环境 export const isServer = typeof window === 'undefined'; export default async function initTranslations( locale: any, namespaces: string[], i18nInstance?: any, ) { i18nInstance = i18nInstance || createInstance(); i18nInstance.use(initReactI18next); // 仅客户端启用LanguageDetector if (!isServer) { i18nInstance.use(LanguageDetector); } await i18nInstance.init({ lng: locale, // 服务端禁用检测器,强制使用传入的locale detection: isServer ? { enabled: false } : options, resources, fallbackLng: i18nConfig.defaultLocale, supportedLngs: i18nConfig.locales, defaultNS: namespaces[0], fallbackNS: namespaces[0], ns: namespaces, // 服务端预加载当前请求的locale资源 preload: isServer ? [locale] : (resources ? [] : i18nConfig.locales) }); return { i18n: i18nInstance, resources: i18nInstance.services.resourceStore.data, t: i18nInstance.t }; }
3. 在根Layout中统一注入TranslationsProvider
App Router架构下,建议在根layout.tsx中统一配置Provider,避免每个页面重复初始化,确保全局locale一致:
import TranslationsProvider from './TranslationsProvider'; import { useParams } from 'next/navigation'; export default function RootLayout({ children, }: { children: React.ReactNode; }) { const params = useParams<{ locale: string }>(); return ( <html lang={params.locale}> <body> <TranslationsProvider locale={params.locale} namespaces={['common']}> {children} </TranslationsProvider> </body> </html> ); }
4. 标记翻译组件为客户端组件
所有使用useTranslation等react-i18next钩子的组件,必须添加'use client'指令,否则在Server Components中会报错,同时引发水化不匹配:
'use client'; import { useTranslation } from 'react-i18next'; export default function Navbar() { const { t } = useTranslation('common'); return <nav>{t('navbar.title')}</nav>; }
内容的提问来源于stack exchange,提问作者gene b.

