Next.js页面初始加载样式失效,图标过大CSS错乱求助
问题描述
按照Next.js + MUI5官方示例开发页面,页面加载时出现图标过大、CSS完全错乱的问题。
依赖版本
{ "next": "^12.3.1", "react": "^18.2.0", "@emotion/react": "^11.10.4", "@emotion/server": "^11.10.0", "@emotion/styled": "^11.10.4", "@mui/icons-material": "^5.10.6", "@mui/lab": "^5.0.0-alpha.102", "@mui/material": "^5.10.8", "@mui/styles": "^5.10.8" }
问题演示

相关代码
_app.js
import React from 'react'; import PropTypes from 'prop-types'; import Head from 'next/head'; import { CacheProvider } from '@emotion/react'; import CssBaseline from '@mui/material/CssBaseline'; import Layout from '../components/layout'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; import LinearProgress from '@mui/material/LinearProgress'; import * as gtag from '../lib/googleAnalytics'; import '../styles/globals.css'; import createEmotionCache from '../components/theme/createEmotionCache'; // Client-side cache, shared for the whole session of the user in the browser. const clientSideEmotionCache = createEmotionCache(); export default function NCD(props) { const { Component, emotionCache = clientSideEmotionCache, pageProps } = props; const [isLoading, setLoadingState] = useState(false); const router = useRouter(); useEffect(() => { const handleRouteChange = (url) => { gtag.pageview(url) } router.events.on('routeChangeStart', () => { setLoadingState(true); }); router.events.on('routeChangeComplete', () => { handleRouteChange; setLoadingState(false); }); router.events.on('routeChangeError', () => { setLoadingState(false); }); if (typeof window == 'undefined') { setLoadingState(true); } // Remove the server-side injected CSS. const jssStyles = document.querySelector('#jss-server-side'); if (jssStyles) { jssStyles.parentElement.removeChild(jssStyles); } }, []); return ( <React.Fragment> <Head> <title>{process.env.SITENAME}</title> <meta name="viewport" content="initial-scale=1, width=device-width" /> </Head> <CacheProvider value={emotionCache}> {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} <CssBaseline /> <Layout> {isLoading? ( <> <div className="pageLoader"> <LinearProgress /> </div> </>):( <> <Component {...pageProps} /> </> )} </Layout> </CacheProvider> </React.Fragment> ); } NCD.propTypes = { Component: PropTypes.elementType.isRequired, pageProps: PropTypes.object.isRequired, };
_document.js
import * as React from 'react'; import Document, { Html, Head, Main, NextScript } from 'next/document'; import createEmotionServer from '@emotion/server/create-instance'; import createEmotionCache from '../components/theme/createEmotionCache'; import { GA_TRACKING_ID } from '../lib/googleAnalytics'; import ServerStyleSheets from '@mui/styles/ServerStyleSheets'; export default class MyDocument extends Document { render() { return ( <Html lang='en'> <Head> <link rel="shortcut icon" href="/favicon.ico" /> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" /> {/* Global Site Tag (gtag.js) - Google Analytics */} <script async src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`} /> <script dangerouslySetInnerHTML={{ __html: ` window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', '${GA_TRACKING_ID}', { page_path: window.location.pathname, }); `, }} /> </Head> <body> <Main /> <NextScript /> </body> </Html> ) } } MyDocument.getInitialProps = async (ctx) => { const originalRenderPage = ctx.renderPage; const cache = createEmotionCache(); const { extractCriticalToChunks } = createEmotionServer(cache); const sheets = new ServerStyleSheets() ctx.renderPage = () => originalRenderPage({ enhanceApp: (App) => function EnhanceApp(props) { return sheets.collect(<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, }; };
Layout.js
import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles'; import CssBaseline from '@mui/material/CssBaseline'; import {lightTheme, darkTheme} from '../components/theme'; import TopAppBar from './ui/topAppBar'; import { useCookies } from 'react-cookie'; import Copyright from '../components/ui/copyright'; import Box from '@mui/material/Box'; const layout = ({ children }) => { const [cookies, setCookie] = useCookies(['darkTheme']); return <> <StyledEngineProvider injectFirst> <ThemeProvider theme={cookies.darkTheme=="true" ? darkTheme : lightTheme}> <CssBaseline /> <header> <nav> <TopAppBar /> </nav> </header> <main>{children}</main> <Box mt={8} mb={4} > <Copyright /> </Box> </ThemeProvider> </StyledEngineProvider> </>; }; export default layout;
解决方案
核心问题
代码混合使用了MUI旧版JSS样式方案(@mui/styles)和新版Emotion样式方案,且_document.js中样式收集逻辑冲突,导致服务端与客户端样式不匹配,引发CSS错乱、图标异常。
具体修复步骤
1. 移除旧版JSS相关依赖和代码
- 删除
package.json中的@mui/styles依赖 - 删除
_document.js中ServerStyleSheets的导入及相关收集代码
2. 修正_document.js的样式收集逻辑
修改getInitialProps方法,仅保留Emotion服务端样式处理:
MyDocument.getInitialProps = async (ctx) => { const originalRenderPage = ctx.renderPage; const cache = createEmotionCache(); const { extractCriticalToChunks } = createEmotionServer(cache); ctx.renderPage = () => originalRenderPage({ enhanceApp: (App) => (props) => <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, }; };
3. 调整_app.js的结构和冗余代码
- 删除针对
#jss-server-side的移除代码(不再使用JSS) - 将
StyledEngineProvider移至CacheProvider内部,确保样式优先级正确:
return ( <React.Fragment> <Head> <title>{process.env.SITENAME}</title> <meta name="viewport" content="initial-scale=1, width=device-width" /> </Head> <CacheProvider value={emotionCache}> <StyledEngineProvider injectFirst> <CssBaseline /> <Layout> {isLoading ? ( <> <div className="pageLoader"> <LinearProgress /> </div> </> ) : ( <> <Component {...pageProps} /> </> )} </Layout> </StyledEngineProvider> </CacheProvider> </React.Fragment> );
4. 清理Layout.js的冗余组件
删除Layout.js中的StyledEngineProvider:
import { ThemeProvider } from '@mui/material/styles'; import CssBaseline from '@mui/material/CssBaseline'; import {lightTheme, darkTheme} from '../components/theme'; import TopAppBar from './ui/topAppBar'; import { useCookies } from 'react-cookie'; import Copyright from '../components/ui/copyright'; import Box from '@mui/material/Box'; const layout = ({ children }) => { const [cookies, setCookie] = useCookies(['darkTheme']); return <> <ThemeProvider theme={cookies.darkTheme=="true" ? darkTheme : lightTheme}> <CssBaseline /> <header> <nav> <TopAppBar /> </nav> </header> <main>{children}</main> <Box mt={8} mb={4} > <Copyright /> </Box> </ThemeProvider> </>; }; export default layout;
5. 重装依赖
删除node_modules和锁文件后重新安装:
rm -rf node_modules package-lock.json npm install
内容的提问来源于stack exchange,提问作者Kal
相关产品推荐
相关产品推荐

