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

Next.js搭配Emotion时服务端与浏览器样式差异导致客户端样式不更新

问题根因

该问题是Next.js SSR 结合 Emotion 11 时的hydration阶段典型冲突,核心触发逻辑如下:

  • React hydration阶段默认优先复用服务端返回的DOM节点,只要没有显式抛出不匹配错误,不会主动更新服务端渲染生成的class属性
  • Emotion服务端渲染时生成的样式会提前注入页面head,优先级高于客户端hydrate同步生成的同权重样式,即使客户端生成了新的样式类,旧的服务端样式也会覆盖新样式
  • 直接使用process.browser这类仅单端存在的变量控制样式,会导致服务端和客户端初始渲染生成的类名不一致,放大上述冲突
可行解决方案

方案1:组件仅在客户端渲染

使用next/dynamic包裹目标组件,关闭服务端渲染能力,从根源避免两端样式不一致的问题:

import dynamic from 'next/dynamic'

// 替换为你的组件路径
const DynamicBox = dynamic(() => import('../components/ColoredBox'), {
  ssr: false,
  // 可选:配置加载时的占位组件
  loading: () => <div style={{ background: 'red', width: 200, height: 200 }} />
})

方案2:useEffect触发客户端重渲染

在组件内部声明本地状态,在hydrate完成后执行的useEffect中更新状态触发重渲染,此时会正确读取浏览器端环境参数生成对应样式:

import { useEffect, useState } from 'react'
import styled from '@emotion/styled'

const ColoredBox = styled.div`
  width: 200px;
  height: 200px;
  background: ${props => props.isBrowser ? 'blue' : 'red'};
`

export default function Home() {
  const [isClient, setIsClient] = useState(false)

  useEffect(() => {
    setIsClient(true)
  }, [])

  return <ColoredBox isBrowser={isClient} />
}

方案3:补全Emotion SSR配置

如果你需要保留组件的服务端渲染能力,首先确认已经在pages/_document.js中完成Emotion的服务端样式提取配置,避免两端样式注入逻辑不一致:

import Document, { Html, Head, Main, NextScript } from 'next/document'
import createCache from '@emotion/cache'
import { CacheProvider } from '@emotion/react'
import { renderToString } from 'react-dom/server'

const emotionCache = createCache({ key: 'emotion-css' })

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const originalRenderPage = ctx.renderPage

    ctx.renderPage = () =>
      originalRenderPage({
        enhanceApp: (App) => (props) =>
          <CacheProvider value={emotionCache}>
            <App {...props} />
          </CacheProvider>,
      })

    const initialProps = await Document.getInitialProps(ctx)
    const emotionStyles = renderToString(
      <style 
        data-emotion={`${emotionCache.key} ${Object.keys(emotionCache.inserted).join(' ')}`}
        dangerouslySetInnerHTML={{ __html: Object.values(emotionCache.inserted).join('') }}
      />
    )

    return {
      ...initialProps,
      styles: (
        <>
          {initialProps.styles}
          {emotionStyles}
        </>
      ),
    }
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

内容的提问来源于stack exchange,提问作者Brendan Morrell

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 09:51:03