Remix集成Material UI时出现React Hydration错误求助
Remix + Material UI 服务端渲染 Hydration 错误排查与修复
核心问题定位
你遇到的是服务端与客户端 Emotion 缓存实例未按请求隔离导致的 Hydration 不匹配:首次启动服务器时缓存是全新的,能正常渲染;但后续刷新请求复用了全局缓存实例,导致服务端生成的 CSS 与客户端预期不一致,触发 HTML 不匹配错误。
分步修复方案
1. 为每个请求创建独立的 Emotion 缓存实例
修改 Entry.Server.tsx,确保缓存实例在请求处理函数内部创建,避免模块级全局复用:
// Entry.Server.tsx import { createCache } from '@emotion/cache'; import { CacheProvider } from '@emotion/react'; import { RemixServer } from '@remix-run/react'; import { renderToString } from 'react-dom/server'; import type { EntryContext } from '@remix-run/node'; export default function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext ) { // 关键:每个请求单独创建缓存 const cache = createCache({ key: 'mui-ssr' }); cache.compat = true; const html = renderToString( <CacheProvider value={cache}> <RemixServer context={remixContext} url={request.url} /> </CacheProvider> ); // 提取服务端渲染的 CSS 并设置响应头 const styles = cache.inserted; if (styles) { responseHeaders.set('X-Emotion-Cache-Key', cache.key); } return new Response(`<!DOCTYPE html>${html}`, { headers: responseHeaders, status: responseStatusCode, }); }
2. 客户端缓存与服务端缓存同步
修改 Entry.Client.tsx,读取服务端返回的缓存标识,确保客户端使用一致的缓存实例:
// Entry.Client.tsx import { createCache } from '@emotion/cache'; import { CacheProvider } from '@emotion/react'; import { RemixBrowser } from '@remix-run/react'; import { startTransition, StrictMode } from 'react'; import { hydrateRoot } from 'react-dom/client'; function hydrate() { startTransition(() => { // 读取服务端传递的缓存 key const cacheKey = document.querySelector('meta[name="emotion-cache-key"]')?.getAttribute('content') || 'mui-ssr'; const cache = createCache({ key: cacheKey }); cache.compat = true; hydrateRoot( document, <StrictMode> <CacheProvider value={cache}> <RemixBrowser /> </CacheProvider> </StrictMode> ); }); } if (window.requestIdleCallback) { window.requestIdleCallback(hydrate); } else { setTimeout(hydrate, 1); }
3. 修正 Root.tsx 中的缓存与主题层级
确保 Material UI 的 ThemeProvider 被正确包裹,且不在 Root 中重复定义 CacheProvider:
// Root.tsx import { ThemeProvider, createTheme } from '@mui/material/styles'; import CssBaseline from '@mui/material/CssBaseline'; import type { LinksFunction } from '@remix-run/node'; import { Meta, Links, Outlet, ScrollRestoration, Scripts, LiveReload } from '@remix-run/react'; export const links: LinksFunction = () => [ { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossOrigin: 'anonymous' }, ]; export default function App() { const theme = createTheme(); return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> {/* 新增 meta 标签传递缓存 key 给客户端 */} <meta name="emotion-cache-key" content="mui-ssr" /> <Meta /> <Links /> </head> <body> <ThemeProvider theme={theme}> <CssBaseline /> <Outlet /> </ThemeProvider> <ScrollRestoration /> <Scripts /> <LiveReload /> </body> </html> ); }
4. 验证修复效果
执行以下步骤确保环境干净:
- 删除
node_modules:rm -rf node_modules - 清空 npm 缓存:
npm cache clean --force - 重新安装依赖:
npm install - 重启开发服务器:
npm run dev
刷新页面观察错误是否消失,如果仍出现问题,排查以下点:
- 是否有组件在服务端渲染时调用了
window/document等客户端专属 API - 是否有动态样式的生成逻辑在服务端与客户端不一致
- 是否有第三方库干扰了 Emotion 缓存的正常工作
内容的提问来源于stack exchange,提问作者Darrell Washington
相关产品推荐
相关产品推荐

