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

