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

