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
相关产品推荐
相关产品推荐

