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

Next.js生产构建报错 开发模式正常(styled-components等技术栈)

问题原因

开发模式下样式由客户端动态注入,不会触发服务端完整的样式收集与预渲染校验流程,因此不会出现报错。生产构建报错的核心诱因有四点:

  • 同时使用Emotion和styled-components两套CSS-in-JS方案,但_document.tsx仅实现了Emotion的SSR样式收集逻辑,完全没有处理styled-components的服务端样式提取,生产构建预渲染阶段会出现样式hydration不匹配、DOM结构校验失败的问题。
  • Babel插件配置顺序错误:Babel插件按从后往前的顺序执行,当前配置中babel-plugin-macros(twin.macro依赖该插件)放在插件列表末尾,会导致twin.macro的语法转换在Emotion、styled-components语法处理之后才执行,生产模式编译时出现语法解析错误。
  • _document.tsx中Emotion的extractCritical调用逻辑存在缺陷:直接对Next.js原生渲染完成的完整HTML字符串做关键样式提取,会误处理页面中已经存在的styled-components生成的style标签内容,打乱原有DOM结构,直接触发生产构建报错。
  • 关闭swcMinify未生效的原因是项目使用Babel编译链,生产环境默认会启用Terser做代码压缩,默认压缩规则会抹除CSS-in-JS依赖的/*#__PURE__*/纯函数标记,进一步导致样式注入逻辑异常。
修复方案

1. 修正_document.tsx的SSR样式收集逻辑

同时收集Emotion和styled-components的服务端样式,避免单一样式处理导致的DOM冲突,替换原有代码为以下内容:

import { extractCritical } from "@emotion/server"
import Document, { Head, Html, Main, NextScript, DocumentContext } from "next/document"
import { ServerStyleSheet } from "styled-components"
import React from "react"

export default class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    // 初始化styled-components服务端样式收集实例
    const sheet = new ServerStyleSheet()
    const originalRenderPage = ctx.renderPage

    try {
      // 重写renderPage,包裹样式收集组件
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),
        })

      const initialProps = await Document.getInitialProps(ctx)
      const critical = extractCritical(initialProps.html)
      
      return {
        ...initialProps,
        html: critical.html,
        styles: (
          <React.Fragment>
            {initialProps.styles}
            {/* 注入styled-components收集到的样式 */}
            {sheet.getStyleElement()}
            {/* 注入Emotion关键样式 */}
            <style
              data-emotion-css={critical.ids.join(" ")}
              dangerouslySetInnerHTML={{ __html: critical.css }}
            />
          </React.Fragment>
        ),
      }
    } finally {
      sheet.seal()
    }
  }

  render() {
    return (
      <Html lang="en">
        <Head>
          <meta name="viewport" content="width=device-width,initial-scale=1" />
          <link rel="preconnect" href="https://fonts.googleapis.com" />
          <link
            rel="preconnect"
            href="https://fonts.gstatic.com"
            crossOrigin="true"
          />
          <link
            href="https://fonts.googleapis.com/css2?family=Inter:wght@100;300;400;500;600;700&display=swap"
            rel="stylesheet"
          />
          <link rel="shortcut icon" href="/images/icons/nsorcell.svg" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

2. 调整.babelrc插件顺序与配置

修正插件执行顺序,补充styled-components编译优化配置,避免多CSS-in-JS方案编译冲突,替换原有配置为以下内容:

{
  "presets": [
    [
      "next/babel",
      {
        "preset-react": {
          "runtime": "automatic",
          "importSource": "@emotion/react"
        }
      }
    ]
  ],
  "plugins": [
    "babel-plugin-macros",
    "@emotion/babel-plugin",
    ["styled-components", { "ssr": true, "displayName": false, "pure": true }]
  ]
}

如果项目中twin.macro默认使用styled-components作为样式输出目标,需要在项目根目录新增twin.config.js配置指定Emotion为输出目标,避免语法编译冲突:

// twin.config.js
module.exports = {
  preset: "emotion",
  autoCssProp: true,
}

3. 调整next.config.js压缩配置

保留CSS-in-JS依赖的纯函数标记,避免压缩导致的逻辑异常,修改配置如下:

/** @type {import('next').NextConfig} */
const { i18n } = require("./next-i18next.config")

const nextConfig = {
  reactStrictMode: true,
  swcMinify: false,
  i18n,
  env: {
    ALCHEMY_RPC_MAINNET: process.env.ALCHEMY_RPC_MAINNET,
    ALCHEMY_RPC_GOERLI: process.env.ALCHEMY_RPC_GOERLI,
    ALCHEMY_RPC_POLYGON_MAINNET: process.env.ALCHEMY_RPC_POLYGON_MAINNET,
    ALCHEMY_RPC_MUMBAI: process.env.ALCHEMY_RPC_MUMBAI,
  },
  webpack: (config) => {
    // 调整压缩配置,保留CSS-in-JS依赖的纯函数标记
    config.optimization.minimizer.forEach((minimizer) => {
      if (minimizer.constructor.name === "TerserPlugin") {
        minimizer.options.terserOptions.compress.pure_funcs = []
        minimizer.options.terserOptions.format.comments = /@__PURE__/
      }
    })
    return config
  }
}

module.exports = nextConfig

4. 清理缓存重新构建

执行以下命令删除旧的构建缓存,重新执行生产构建即可:

rm -rf .next
npm run build

内容的提问来源于stack exchange,提问作者Daniel Tok

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 12:01:48