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

NextJS+I18Next切换语言后SSR触发Hydration错误解决方案咨询

解决方案:让SSR识别用户选中的语言,避免Hydration错误

核心思路是将用户选中的语言存入Cookie,服务端渲染时读取Cookie值初始化多语言配置,确保SSR与客户端使用同一语言,消除内容不一致问题。以下是具体实现步骤:


1. 修改语言切换逻辑,同步写入Cookie

切换语言时,除了调用i18n.changeLanguage,还要将语言代码写入Cookie(确保服务端能读取到):

import Cookies from 'js-cookie'; // 或使用原生document.cookie
import { useTranslation } from 'react-i18next';
import { useUserStore } from '@/stores/userStore';

const changeLanguage = async (langCode) => {
  const { i18n } = useTranslation();
  const userStore = useUserStore();
  
  await i18n.changeLanguage(langCode);
  // 写入Cookie,有效期30天,路径覆盖全站
  Cookies.set('user-language', langCode, { expires: 30, path: '/' });
  // 同步更新本地存储与状态管理
  localStorage.setItem('user-language', langCode);
  userStore.set({ languageKey: langCode });
};

2. 根布局中读取Cookie,传递正确语言给初始化逻辑

在layout.tsx中,通过Next.js的headers()函数获取请求头里的Cookie,解析出用户语言,并用该值初始化翻译资源:

import { headers } from 'next/headers';
import initTranslations from '@/utils/initTranslations';
import TranslationsProvider from '@/components/TranslationsProvider';
import i18nConfig from './next-18next.config';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  // 从请求头解析Cookie
  const headersList = headers();
  const cookieHeader = headersList.get('cookie');
  let userLocale = i18nConfig.defaultLocale;

  if (cookieHeader) {
    const cookies = Object.fromEntries(
      cookieHeader.split(';').map(cookie => {
        const [key, value] = cookie.trim().split('=');
        return [key, decodeURIComponent(value)];
      })
    );
    // 验证语言是否在支持列表中
    if (cookies['user-language'] && i18nConfig.locales.includes(cookies['user-language'])) {
      userLocale = cookies['user-language'];
    }
  }

  // 用用户语言初始化翻译资源
  const { resources } = await initTranslations(undefined, userLocale);

  return (
    <html lang={userLocale}> {/* 同步设置html标签的lang属性 */}
      <body>
        <main>
          <TranslationsProvider 
            resources={resources} 
            locale={userLocale} 
            namespaces={null}
          >
            {children}
          </TranslationsProvider>
        </main>
      </body>
    </html>
  );
}

3. 调整TranslationProvider,对齐服务端与客户端语言

修改TranslationsProvider,优先使用服务端传递的语言,客户端再根据用户存储做同步:

import { useEffect, useState } from 'react';
import { I18nextProvider } from 'react-i18next';
import createInstance from 'i18next';
import initTranslations from '@/utils/initTranslations';
import { useUserStore } from '@/stores/userStore';
import i18nConfig from '../next-18next.config';

export default function TranslationsProvider({ children, locale: serverLocale, namespaces, resources }) {
  const i18n = createInstance();
  const user = useUserStore();
  const [clientLocale, setClientLocale] = useState(serverLocale);

  useEffect(() => {
    // 客户端渲染时,若用户存储的语言与服务端不一致则更新
    if (user?.languageKey && i18nConfig.locales.includes(user.languageKey) && user.languageKey !== clientLocale) {
      setClientLocale(user.languageKey);
      initTranslations(namespaces, user.languageKey, i18n, resources);
    }
  }, [user, clientLocale, namespaces, resources, i18n]);

  // 最终语言优先级:服务端传递值 > 用户存储值 > 默认语言
  const finalLocale = clientLocale ?? user?.languageKey ?? i18nConfig.defaultLocale;
  initTranslations(namespaces, finalLocale, i18n, resources);

  return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
}

4. 优化initTranslations函数(可选)

调整资源加载逻辑,避免不必要的预加载,同时修正资源路径(若原路径不符合规范):

import createInstance from 'i18next';
import initReactI18next from 'react-i18next';
import resourcesToBackend from 'i18next-resources-to-backend';
import i18nConfig from '../next-18next.config';

export default async function initTranslations(namespaces, locale, i18nInstance, resources) {
  locale = locale ?? i18nConfig.defaultLocale;
  namespaces = namespaces ?? ["core", "ui"];
  i18nInstance = i18nInstance || createInstance();
  i18nInstance.use(initReactI18next);

  if (!resources) {
    i18nInstance.use(
      resourcesToBackend(
        // 假设翻译文件结构为 locales/texts/en/core.json
        (language, namespace) => import(`@/locales/texts/${language}/${namespace}.json`),
      ),
    );
  }

  await i18nInstance.init({
    lng: locale,
    resources,
    fallbackLng: i18nConfig.defaultLocale,
    supportedLngs: i18nConfig.locales,
    defaultNS: "",
    fallbackNS: "",
    ns: namespaces,
    preload: resources ? [] : [locale], // 仅预加载当前语言,优化性能
    interpolation: { escapeValue: false }, // React已处理转义,无需重复操作
  });

  return {
    i18n: i18nInstance,
    resources: i18nInstance.services.resourceStore.data,
    t: i18nInstance.t,
  };
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 09:31:05