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

Next.js:如何在_document.js/_app.js中动态插入关键CSS?——基于CSS Modules的多主题环境变量切换实现问询

Great question! Let's break this down based on whether you need to pick the theme at build time (for full tree-shaking of unused themes) or runtime (per-request server-side decision).


Scenario 1: Build-Time Theme Selection (Full Tree-Shaking)

If your theme is determined by environment variables at build time (e.g., production uses default, staging uses dark), this is the most efficient approach—Webpack will only bundle the selected theme and completely tree-shake the rest.

Step 1: Organize Your Theme Files

Store your theme CSS files in a dedicated directory, e.g., styles/themes/:

/* styles/themes/theme-default.css */
:root {
  --PRIMARY_COLOR: #504f94;
  --PRIMARY_BRIGHT_COLOR: #926ba7;
  --PRIMARY_COLOR_ACCENT: #3e3d81;
  --SECONDARY_COLOR: #102f31;
  --SECONDARY_COLOR_ACCENT: #1d2020;
}
/* styles/themes/theme-dark.css */
:root {
  --PRIMARY_COLOR: #2d2c5e;
  --PRIMARY_BRIGHT_COLOR: #7a508f;
  --PRIMARY_COLOR_ACCENT: #22214f;
  --SECONDARY_COLOR: #0a1f20;
  --SECONDARY_COLOR_ACCENT: #121515;
}

Step 2: Configure Environment Variables

Add your theme variable to next.config.js so it’s available at build time:

// next.config.js
module.exports = {
  env: {
    APP_THEME: process.env.APP_THEME || 'default',
  },
};

Set the APP_THEME variable in your build command (e.g., APP_THEME=dark npm run build).

Step 3: Import the Theme in _app.js

Use a static condition to import only the selected theme—Webpack will tree-shake unused files:

// pages/_app.js
// Static condition (Webpack can analyze this at build time)
if (process.env.APP_THEME === 'dark') {
  import '../styles/themes/theme-dark.css';
} else {
  import '../styles/themes/theme-default.css';
}

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

export default MyApp;

Scenario 2: Runtime Server-Side Theme Selection (Per-Request Decision)

If you need to pick a theme dynamically per request (e.g., based on user preferences, request headers, or domain), you can inject the theme directly into the server-rendered HTML. Unused themes will be bundled as separate chunks but won’t load until requested.

Step 1: Configure Webpack to Load CSS as Strings

Add raw-loader to your Webpack config so you can import theme CSS as plain strings:

// next.config.js
module.exports = {
  webpack: (config) => {
    config.module.rules.push({
      test: /^theme-.+\.css$/,
      use: 'raw-loader',
      include: /styles\/themes/,
    });
    return config;
  },
};

Install raw-loader first: npm install raw-loader --save-dev

Step 2: Inject Theme in _document.js

Use getInitialProps (server-side only) to decide the theme, then inject its CSS directly into the <head>:

// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';

// Map theme names to dynamic import functions
const themeImports = {
  default: () => import('../styles/themes/theme-default.css').then(mod => mod.default),
  dark: () => import('../styles/themes/theme-dark.css').then(mod => mod.default),
};

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    
    // Server-side decision logic: adjust this to your needs
    const requestedTheme = ctx.req.cookies.theme || 'default';
    const validTheme = themeImports[requestedTheme] ? requestedTheme : 'default';
    
    // Load the selected theme CSS
    const themeCss = await themeImports[validTheme]();
    
    return { ...initialProps, themeCss };
  }

  render() {
    return (
      <Html>
        <Head>
          {/* Inject theme CSS directly into the HTML */}
          <style
            id="theme-styles"
            dangerouslySetInnerHTML={{ __html: this.props.themeCss }}
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

Key Notes

  • Tree-shaking only works for build-time selection: Runtime selection can’t avoid bundling all themes, but they’re split into separate chunks that don’t affect initial load performance.
  • Avoid client-side dynamic imports: Server-side injection prevents CSS flash, as the theme is present in the rendered HTML from the start.
  • Global CSS variables work with CSS Modules: Your theme’s :root variables will apply globally, even if you use CSS Modules for component styles.

内容的提问来源于stack exchange,提问作者Alexander Knyazev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 16:22:33