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

Next.js+Material UI+Emotion 动态RTL/LTR类名重复问题求助

动态切换RTL/LTR时MUI组件重复类名问题解决

使用Next.js v12.2、@mui/material v5.9、next-i18next v11.3实现多语言RTL/LTR切换时,出现MUI组件生成重复类名的问题,核心原因是服务端渲染时未根据当前locale创建对应方向的Emotion缓存,导致服务端生成的LTR样式缓存与客户端切换RTL后创建的新缓存并存,进而产生重复类名。

以下是针对性的修复方案:

1. 修复服务端Document的Emotion缓存创建

在_document.js的getInitialProps中,从ctx获取当前locale,传入createEmotionCache创建对应方向的缓存,确保服务端渲染的样式与当前语言方向一致:

import * as React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import createEmotionServer from "@emotion/server/create-instance";
import theme from "theme";
import createEmotionCache from "theme/createEmotionCache";
import i18nextConfig from "next-i18next.config";

export default class MyDocument extends Document {
  render() {
    const { locale } = this.props.__NEXT_DATA__;
    const dir = locale === "fa" ? "rtl" : "ltr"; // 根据你的语言配置调整判断逻辑

    return (
      <Html lang={locale} dir={dir}>
        <Head>
          <meta name="theme-color" content={theme.palette.primary.main} />
          <meta name="emotion-insertion-point" content="" />
          {this.props.emotionStyleTags}
        </Head>
        <body dir={dir}>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

MyDocument.getInitialProps = async ctx => {
  const originalRenderPage = ctx.renderPage;
  // 从ctx获取当前locale,确定方向
  const locale = ctx.locale || ctx.defaultLocale;
  const direction = locale === "fa" ? "rtl" : "ltr";
  // 创建对应方向的Emotion缓存
  const cache = createEmotionCache(direction);
  const { extractCriticalToChunks } = createEmotionServer(cache);

  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: App =>
        function EnhanceApp(props) {
          return <App emotionCache={cache} {...props} />;
        },
    });

  const initialProps = await Document.getInitialProps(ctx);
 
  const emotionStyles = extractCriticalToChunks(initialProps.html);
  const emotionStyleTags = emotionStyles.styles.map(style => (
    <style
      data-emotion={`${style.key} ${style.ids.join(" ")}`}
      key={style.key}
      dangerouslySetInnerHTML={{ __html: style.css }}
    />
  ));

  return {
    ...initialProps,
    emotionStyleTags,
  };
};

2. 优化App组件的缓存切换逻辑

在_app.js中,避免每次语言切换时重复创建缓存,仅在方向变化时更新缓存,同时优先使用服务端传递的缓存:

import Head from "next/head";
import { appWithTranslation, useTranslation } from "next-i18next";
import { ThemeProvider } from "@mui/material/styles";
import { CacheProvider } from "@emotion/react";
import CssBaseline from "@mui/material/CssBaseline";
import createEmotionCache from "theme/createEmotionCache";
import theme from "theme";
import "styles/globals.scss";
import { useEffect, useState } from "react";

function MyApp(props) {
  const { i18n } = useTranslation();
  const {
    Component,
    emotionCache: serverEmotionCache,
    pageProps,
  } = props;

  // 初始化客户端缓存,优先使用服务端传递的缓存
  const [clientEmotionCache, setClientEmotionCache] = useState(() => 
    serverEmotionCache || createEmotionCache(i18n.dir())
  );

  useEffect(() => {
    const newDir = i18n.dir();
    const targetCacheKey = newDir === "rtl" ? "mui-style-rtl" : "mui-style-ltr";
    
    // 方向变化时重新创建缓存
    if (clientEmotionCache.key !== targetCacheKey) {
      setClientEmotionCache(createEmotionCache(newDir));
    }

    // 同步更新全局方向属性
    document.body.dir = newDir;
    document.documentElement.dir = newDir;
  }, [i18n, clientEmotionCache.key]);

  const currentCache = serverEmotionCache || clientEmotionCache;

  return (
    <CacheProvider value={currentCache}>
      <ThemeProvider theme={{ ...theme, direction: i18n.dir() }}>
        <Head>
          <meta
            name="viewport"
            content="width=device-width, initial-scale=1.0"
          />
        </Head>
        <CssBaseline />
        <Component {...pageProps} />
      </ThemeProvider>
    </CacheProvider>
  );
}

export default appWithTranslation(MyApp);

3. 确认createEmotionCache逻辑正确性

确保createEmotionCache.js根据方向生成不同key的缓存,避免样式冲突:

import createCache from "@emotion/cache";
import { prefixer } from "stylis";
import rtlPlugin from "stylis-plugin-rtl";

const isBrowser = typeof document !== "undefined";

export default function createEmotionCache(direction) {
  let insertionPoint;

  if (isBrowser) {
    const emotionInsertionPoint = document.querySelector(
      'meta[name="emotion-insertion-point"]',
    );
    insertionPoint = emotionInsertionPoint ?? undefined;
  }

  return createCache({
    key: direction === "rtl" ? "mui-style-rtl" : "mui-style-ltr",
    stylisPlugins: direction === "rtl" ? [prefixer, rtlPlugin] : [],
    insertionPoint,
  });
}

修复原理

  • 服务端渲染时,根据当前locale创建对应方向的Emotion缓存,确保服务端输出的样式与语言方向匹配,避免客户端加载不匹配的服务端样式
  • 客户端切换语言时,仅在方向变化时重新创建缓存,确保同一时间只有一个方向的缓存生效,避免重复类名生成
  • 同步更新根元素和body的dir属性,保证全局文本方向一致

内容的提问来源于stack exchange,提问作者hmd.fullstack

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 21:36:26