解决未升级到next-i18next时因Layout.tsx导致的在切换语言时出现的react-i18next hydration错误

前端开发 2026-07-10

在语言切换之间刷新或重定向到页面时,我遇到了一个 react-i18next 水合错误。

水合失败,因为服务器渲染的文本与客户端不匹配。
因此,这个树结构将在客户端重新生成。若使用了SSR的客户端组件:

然而,将其升级到 next-18next推荐解决方案 对我不起作用。它需要 appWithTranslation(App) 的封装,我们由于 layout.tsx 的做法而无法提供。

据Google指出:

appWithTranslation HOC与 App Router的 layout.tsx不兼容,因为 Server Components 不以相同的方式支持HOCs。相反,next-i18next (v16+) 或像next-intl这样的库使用不同的模式。

由于基于布局的 layout.tsx 方式,我们的App组件看起来像:

const App = (props: {page: string}) => { ... }

而不是:

const App = ({ Component, pageProps }: AppProps) => { ... }

这会导致与 appWithTranslation 相关的错误。

在不升级到 next-18next 的情况下,有没有办法消除这个错误?

当前的 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>
  );
}

解决方案

我通过添加以下副作用(effect),在浏览器就绪时(即完成水合状态)设置一个变量,这在第一次初始化的effect [] 时发生。

  const [isHydrated, setIsHydrated] = useState(false);
  useEffect(() => {
    setIsHydrated(true);
  }, []);

  // In Render code
  if (!isHydrated) return null;

  return(..); // Normal render after check

这可以把它做成一个钩子。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章