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

无需升级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.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 13:27:29