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

