刷新页面后SSR与客户端翻译不匹配引发Hydration错误如何解决?
技术栈版本
- Next.js: 15.1.2
- React: 18.3.1
- i18next: 25.2.1
- next-i18next: 15.4.2
- react-i18next: 15.5.3
问题描述
客户端翻译功能正常,但刷新页面(尤其生产环境)出现以下错误:
Hydration failed because the server rendered HTML didn't match the client...
已确认next-i18next.config.js配置正确,且在getStaticProps中使用了serverSideTranslations,但仍存在翻译不匹配。
解决方案
1. 确保服务端与客户端Locale严格同步
服务端渲染时必须将当前页面的locale准确传递给serverSideTranslations,避免客户端自行判断Locale时出现偏差:
export async function getStaticProps({ locale }) { return { props: { // 确保传入当前页面的locale,且指定对应的命名空间 ...await serverSideTranslations(locale, ['common']), }, }; }
同时页面组件需接收locale作为props,确保客户端使用与服务端一致的Locale值。
2. 调整ready状态的处理逻辑,避免Hydration结构不匹配
当前代码中服务端渲染时ready状态为true(服务端已加载翻译资源),但客户端初始渲染时ready可能为false,导致服务端输出翻译内容、客户端输出Loading...,引发HTML结构不匹配。
修改为服务端直接输出翻译内容,客户端在ready状态未就绪时保持与服务端一致的内容(而非替换成Loading):
import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; export default function YourComponent() { const [clientReady, setClientReady] = useState(false); const { t, ready } = useTranslation('common'); useEffect(() => { if (ready) { setClientReady(true); } }, [ready]); // 服务端直接返回翻译内容,避免Hydration不匹配 if (typeof window === 'undefined') { return <div>{t('your_translation_key')}</div>; } // 客户端未就绪时,仍渲染服务端一致的翻译占位(不要用Loading) if (!clientReady) { return <div>{t('your_translation_key')}</div>; } return <div>{t('your_translation_key')}</div>; }
3. 验证生产环境翻译文件的打包与路径
确保public/locales目录下的翻译文件(如tr/common.json、en/common.json)存在且内容正确,生产环境下next-i18next会将这些文件打包为静态资源,路径错误会导致客户端加载翻译失败,引发不匹配。
4. 检查App Router适配(若使用Next.js 15 App Router)
如果项目使用App Router,需确保服务端组件能正确获取Locale并初始化i18n:
// app/i18n.ts import { createInstance } from 'i18next'; import { initReactI18next } from 'react-i18next'; import resources from '../locales/resources'; export async function getI18nConfig(locale: string) { const i18n = createInstance(); await i18n.use(initReactI18next).init({ lng: locale, resources, react: { useSuspense: false }, }); return i18n; }
在layout组件中传递Locale并初始化i18n实例,确保服务端与客户端的翻译配置完全一致。
5. 临时应急方案:禁用组件SSR(不推荐)
若以上方案均无效,可临时将翻译组件设为纯客户端组件,禁用SSR(会失去SSR优势,仅作应急):
import dynamic from 'next/dynamic'; const TranslatedComponent = dynamic(() => import('./TranslatedComponent'), { ssr: false, });
内容的提问来源于stack exchange,提问作者Seda Babir

