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

Next.js升级React17至React18后SSR不渲染CSS解决方案

问题共性说明

这是Next.js项目从React 17升级React 18时,搭配MUI+Emotion做SSR的高频常见问题,大量开发者升级后都遇到过首屏样式丢失、样式闪烁、hydration阶段样式错乱的问题。

核心根因

现有代码存在三个和React 18不兼容的点:

  • 仅在_document.tsx的服务端渲染阶段注入了Emotion缓存,客户端hydration阶段没有复用同规则的缓存实例,导致客户端生成的CSS类名和服务端输出的类名不匹配
  • _app.tsx里残留了MUI v4(JSS版本)的服务端样式清理逻辑,React 18下useEffect执行时机和React 17存在差异,会在hydration未完成时提前删除服务端注入的样式,且当前用的Emotion方案根本不会生成id为jss-server-side的标签,这段逻辑本身已经失效
  • _document.tsx里的服务端样式收集逻辑没有适配React 18的新渲染机制,提取的关键CSS不完整,导致首屏加载时部分样式缺失
分步修复方案

1. 修改_app.tsx文件

删除旧的JSS样式清理逻辑,在客户端侧也包裹Emotion的CacheProvider,和服务端使用完全一致的缓存配置,修改后代码参考:

import { CacheProvider } from "@emotion/react";
import { AppProvider } from "contexts/AppContext";
import SettingsProvider from "contexts/SettingContext";
import { NextPage } from "next";
import { AppProps } from "next/app";
import Head from "next/head";
import Router from "next/router";
import { Fragment, ReactElement, ReactNode } from "react";
import MuiTheme from "theme/MuiTheme";
import createEmotionCache from "src/createEmotionCache";

// 客户端侧单独创建缓存实例,全局复用
const clientSideEmotionCache = createEmotionCache();

type MyAppProps = AppProps & {
  Component: NextPage & {
    getLayout?: (page: ReactElement) => ReactNode;
  };
  emotionCache?: ReturnType<typeof createEmotionCache>;
};

const App = ({ Component, pageProps, emotionCache = clientSideEmotionCache }: MyAppProps) => {
  const getLayout = Component.getLayout ?? ((page) => page);

  return (
    <CacheProvider value={emotionCache}>
      <Fragment>
        <SettingsProvider>
          <AppProvider>
            <MuiTheme>
              {getLayout(<Component {...pageProps} />)}
            </MuiTheme>
          </AppProvider>
        </SettingsProvider>
      </Fragment>
    </CacheProvider>
  );
};

export default App;

2. 调整_document.tsx的样式注入逻辑

确保服务端收集到的Emotion样式插入位置正确,不会被客户端加载的样式覆盖,核心修改点是把收集到的样式标签放在Head组件内渲染,修改后关键代码参考:

import { CacheProvider } from "@emotion/react";
import createEmotionServer from "@emotion/server/create-instance";
import Document, { Head, Html, Main, NextScript } from "next/document";
import React from "react";
import createEmotionCache from "../src/createEmotionCache";

export default class Bazar extends Document {
  render() {
    return (
      <Html lang="en">
        <Head>
          <link
            href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700;900&display=swap"
            rel="stylesheet"
          />
          <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
          {/* 直接在此处注入服务端收集的Emotion样式 */}
          {this.props.emotionStyleTags}
        </Head>

        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

Bazar.getInitialProps = async (ctx) => {
  const originalRenderPage = ctx.renderPage;

  const cache = createEmotionCache();
  const { extractCriticalToChunks } = createEmotionServer(cache);

  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App) => (props) =>
        (
          <CacheProvider value={cache}>
            <App {...props} />
          </CacheProvider>
        ),
    });

  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. 校验依赖版本兼容性

确保相关依赖版本满足React 18适配要求,避免版本不兼容导致的异常:

  • react、react-dom版本 >= 18.2.0
  • @emotion/react、@emotion/styled、@emotion/server版本 >= 11.10.0
  • MUI相关包(@mui/material、@mui/system等)版本 >=5.10.0
  • Next.js版本建议升级到12.3.0及以上,对React 18的hydration、SSR逻辑有完整兼容
验证方式

注意:不要在dev开发模式下验证SSR样式问题,dev模式下样式为动态注入,和生产环境SSR逻辑完全不同,验证结果不具备参考性
修复完成后执行生产构建校验:

  • 执行next build完成生产环境构建
  • 执行next start启动生产模式服务
  • 访问页面对照检查:首屏加载无样式闪烁,控制台无hydration mismatch、CSS类名不匹配相关警告,查看页面源代码可看到head节点中存在带data-emotion属性的服务端注入样式标签

内容的提问来源于stack exchange,提问作者Nabed Khan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 23:46:03