Next.js生产环境暗黑模式刷新闪白问题求助
问题
基于Next.js + Tailwind CSS构建的网站,使用next-themes实现主题切换功能。本地开发时无异常,但生产环境下刷新或重载页面会短暂闪现浅色主题。排查发现:生产环境刷新页面时,<html>标签上的dark类与内联样式color-scheme: dark;会短暂消失;而同类使用Next.js与next-themes的网站仅dark类会被移除后重新添加,color-scheme: dark;始终保持,本地开发时本站也表现为此正常状态,推测该差异是导致闪白的原因。
相关代码如下:
_document.tsx
import { Providers } from '@/components/Provider'; import { Head, Html, Main, NextScript } from 'next/document'; export default function Document() { return ( <Html lang="en"> <Head> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" /> <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600&display=swap" rel="stylesheet" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple_touch_icon.png" /> <link rel="icon" type="image/png" sizes="32x32" href="/favicon_32x32.png" /> <link rel="icon" type="image/png" sizes="16x16" href="/favicon_16x16.png" /> <link rel="manifest" href="/site.webmanifest" /> {/* Google Tag Manager */} <script dangerouslySetInnerHTML={{ __html: `(function(w,l){` + `w[l] = w[l] || [];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});` + `})(window,'dataLayer');`, }} /> {/* eslint-disable-next-line @next/next/no-sync-scripts */} <script src="/__ENV.js" /> </Head> <body className="overflow-x-hidden dark:bg-black-300"> <Providers> <Main /> <NextScript /> </Providers> </body> </Html> ); }
_app.tsx
import '@/styles/globals.css'; import type { AppProps } from 'next/app'; import dynamic from 'next/dynamic'; import { ThemeProvider } from 'next-themes'; import '../../public/common.css'; import type { NextPageWithLayout } from '@/utils/types'; import Script from 'next/script'; import { env } from 'next-runtime-env'; type AppPropsWithLayout = AppProps & { Component: NextPageWithLayout; }; export default function App({ Component, pageProps }: AppPropsWithLayout) { const getLayout = Component.getLayout ?? ((page) => page); return ( <> <Script src={`https://www.googletagmanager.com/gtm.js?id=${env( 'NEXT_PUBLIC_GTM_ID', )}`} /> <ThemeProvider attribute="class"> {getLayout(<Component {...pageProps} />)} </ThemeProvider> </> ); }
解决方案
核心原因
生产环境下服务端渲染时,next-themes无法提前注入主题相关的类名和样式,导致页面初始渲染为浅色主题,待客户端JS加载完成后才切换到深色主题,从而出现闪白现象。
修复步骤
在
_document.tsx中添加服务端主题预渲染逻辑
通过getInitialProps读取用户存储在Cookie中的主题偏好,直接在<Html>标签上渲染对应的dark类和color-scheme样式,确保页面初始渲染就是正确主题。优化
ThemeProvider配置
明确配置enableSystem和defaultTheme,保证客户端主题切换逻辑与服务端预渲染行为一致。
修改后的_document.tsx
import { Head, Html, Main, NextScript } from 'next/document'; import { getCookie } from 'cookies-next'; export default function Document({ theme }) { return ( <Html lang="en" className={theme === 'dark' ? 'dark' : ''} style={theme === 'dark' ? { colorScheme: 'dark' } : {}} > <Head> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" /> <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600&display=swap" rel="stylesheet" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple_touch_icon.png" /> <link rel="icon" type="image/png" sizes="32x32" href="/favicon_32x32.png" /> <link rel="icon" type="image/png" sizes="16x16" href="/favicon_16x16.png" /> <link rel="manifest" href="/site.webmanifest" /> {/* Google Tag Manager */} <script dangerouslySetInnerHTML={{ __html: `(function(w,l){` + `w[l] = w[l] || [];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});` + `})(window,'dataLayer');`, }} /> {/* eslint-disable-next-line @next/next/no-sync-scripts */} <script src="/__ENV.js" /> </Head> <body className="overflow-x-hidden dark:bg-black-300"> <Main /> <NextScript /> </body> </Html> ); } Document.getInitialProps = async (ctx) => { const initialProps = await (await import('next/document')).Document.getInitialProps(ctx); // 读取next-themes默认存储的theme cookie const theme = getCookie('theme', { req: ctx.req, res: ctx.res }) || 'light'; return { ...initialProps, theme }; };
修改后的_app.tsx
import '@/styles/globals.css'; import type { AppProps } from 'next/app'; import { ThemeProvider } from 'next-themes'; import '../../public/common.css'; import type { NextPageWithLayout } from '@/utils/types'; import Script from 'next/script'; import { env } from 'next-runtime-env'; type AppPropsWithLayout = AppProps & { Component: NextPageWithLayout; }; export default function App({ Component, pageProps }: AppPropsWithLayout) { const getLayout = Component.getLayout ?? ((page) => page); return ( <> <Script src={`https://www.googletagmanager.com/gtm.js?id=${env( 'NEXT_PUBLIC_GTM_ID', )}`} /> <ThemeProvider attribute="class" enableSystem={true} defaultTheme="light" > {getLayout(<Component {...pageProps} />)} </ThemeProvider> </> ); }
额外说明
- 服务端通过
getInitialProps读取用户的主题偏好Cookie,直接在<Html>标签上设置对应属性,避免客户端JS加载前的主题闪烁。 - 移除
_document.tsx中不必要的Providers包裹,ThemeProvider已在_app.tsx中负责客户端的主题切换逻辑。
内容的提问来源于stack exchange,提问作者Ashwin07
相关产品推荐
相关产品推荐

