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

NextJS预加载样式解决FOUC问题:附项目核心代码示例

NextJS + Ant Design 解决FOUC(无样式内容闪烁)问题

问题背景

使用NextJS与Ant Design开发的网站出现FOUC(无样式内容闪烁)问题,需要实现JS加载前预加载样式。项目包含globals.css全局样式文件,同时每个组件使用独立的CSS模块文件。当前/pages/_app.js与/pages/_document.js代码如下:

当前/pages/_app.js代码

import { useEffect } from "react";
import { useRouter } from "next/router";
import Head from "next/head";
import "../styles/globals.css";
import TagManager from "react-gtm-module";
import AppHeader from "../components/MainMenu/header";
import AppHeaderEN from "../components/MainMenu/MenuEN/headerEN";
import ScrollButton from "../components/ScrollToTopWidget/scrollToTopButton";
import CookiesEN from "../components/Cookies/cookiesEn";
import CookiesAF from "../components/Cookies/cookiesAf";
import Footer from "../components/MainFooter/Footer";
import FooterEN from "../components/MainFooter/FooterEN";

function App({ Component, pageProps }) {
  const router = useRouter();

  useEffect(() => {
    if (typeof window !== 'undefined') {
      const tagManagerArgs = {
        gtmId: "GTM-NV63HC6",
      };
      TagManager.initialize(tagManagerArgs);
    }
  }, []);

  useEffect(() => {
    const jssStyles = document.querySelector('#jss-server-side');
    if (jssStyles) {
        jssStyles.parentElement.removeChild(jssStyles);
    }
}, []);

  useEffect(() => {
    const tidioScript = document.createElement("script");
    tidioScript.src = "//code.tidio.co/x6prkzftri0czsnncigtqpailkwuzx3o.js";
    tidioScript.async = true;
    document.head.appendChild(tidioScript);

    return () => {
      document.head.removeChild(tidioScript);
    };
  }, []);

  return (
    <>
    <div className="main">
      <Head>
<script dangerouslySetInnerHTML={{
            __html: `
              !function(f,b,e,v,n,t,s)
              {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
              n.callMethod.apply(n,arguments):n.queue.push(arguments)};
              if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
              n.queue=[];t=b.createElement(e);t.async=!0;
              t.src=v;s=b.getElementsByTagName(e)[0];
              s.parentNode.insertBefore(t,s)}(window,document,'script',
              'https://connect.facebook.net/en_US/fbevents.js');
              fbq('init', '205242273286619');
              fbq('track', 'PageView');
            `,
          }} />
      </Head>
       {router.pathname === "/en" ||
      router.pathname === "/en/" ||
      router.pathname === "/about-us" ||
      router.pathname === "/press-room" ||
      router.pathname === "/benefits" ||
      router.pathname === "/terms-and-conditions" ? (
        <AppHeaderEN />
      ) : (
        <AppHeader />
      )}

      <Component {...pageProps} />
      
      <ScrollButton />
      {router.pathname === "/en" ||
      router.pathname === "/en/" ||
      router.pathname === "/about-us" ||
      router.pathname === "/press-room" ||
      router.pathname === "/benefits" ||
      router.pathname === "/terms-and-conditions" ? (
        <CookiesEN />
      ) : (
        <CookiesAF />
      )}
      {router.pathname === "/en" ||
      router.pathname === "/en/" ||
      router.pathname === "/about-us" ||
      router.pathname === "/press-room" ||
      router.pathname === "/benefits" ||
      router.pathname === "/terms-and-conditions" ? (
        <FooterEN />
      ) : (
        <Footer />
      )}
      </div>
    </>
    
  );
  
}

export default App;

当前/pages/_document.js代码

import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head>
          <link rel="icon" href="https://res.cloudinary.com/brojure-site/image/upload/v1696333014/assets/logos/fav_afqres.ico" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

解决方案

FOUC核心原因是Ant Design的JSS样式未在服务端渲染阶段注入到HTML中,导致页面初始加载时无样式,JS加载完成后才渲染样式。以下是针对性修复步骤:

1. 修改_document.js,实现Ant Design样式的服务端注入

NextJS的_document.js是自定义服务端渲染HTML的入口,需要在这里集成Ant Design的服务端样式收集逻辑,确保样式在HTML返回时就已存在。

替换原/pages/_document.js代码为:

import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet as AntServerStyleSheet, createGenerateClassName } from '@ant-design/cssinjs';

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    // 初始化Ant Design样式收集器
    const antSheet = new AntServerStyleSheet();
    const generateClassName = createGenerateClassName();
    
    try {
      const originalRenderPage = ctx.renderPage;
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            antSheet.collectStyles(<App generateClassName={generateClassName} {...props} />),
        });

      const initialProps = await Document.getInitialProps(ctx);
      
      // 将收集到的样式注入HTML头部
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {antSheet.getStyleElement()}
          </>
        ),
      };
    } finally {
      antSheet.seal();
    }
  }

  render() {
    return (
      <Html>
        <Head>
          <link rel="icon" href="https://res.cloudinary.com/brojure-site/image/upload/v1696333014/assets/logos/fav_afqres.ico" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

2. 确保全局样式优先级

当前_app.js中已正确导入globals.css,NextJS会自动将全局样式打包并在服务端注入到HTML中,无需额外修改,但需保证全局样式导入语句在组件导入之前,确保加载顺序正确。

3. 保留组件CSS模块的正确使用

NextJS的CSS模块默认支持服务端渲染,只要组件按import styles from './Component.module.css'方式导入,就会自动在服务端生成对应样式并注入,无需额外配置。

4. 保留JSS样式清理逻辑

原_app.js中移除#jss-server-side的逻辑是正确的,用于清理服务端注入的样式,避免客户端重复渲染,保留该代码即可。

原理说明

通过在_document.js的getInitialProps中使用Ant Design的ServerStyleSheet收集所有组件的JSS样式,并将这些样式作为<style>标签注入到HTML头部,使得页面加载时就能获取完整样式,避免JS加载完成后才渲染样式导致的闪烁问题。

内容的提问来源于stack exchange,提问作者Kayla-May Smit

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 12:19:54