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

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"
}

问题演示

页面CSS错乱、图标过大的效果

相关代码

_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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 14:45:39