Isomorphic-style-loader是否兼容React 18的renderToPipeableStream?
React 18 SSR中isomorphic-style-loader与renderToPipeableStream兼容问题
问题复现
我们在React SSR框架升级至React 18后,发现isomorphic-style-loader无法与ReactDomServer.renderToPipeableStream配合工作。原代码如下:
服务端代码(server.js)
// server.js const css = new Set(); const StyleContext = require('isomorphic-style-loader/StyleContext'); const insertCss = (...styles) => styles.forEach(style => css.add(style._getCss())); const app = <StyleContext.Provider value={{ insertCss }}><Component /></StyleContext.Provider>; ReactDomServer.renderToPipeableStream( <html> <head> <style>{[...css].join('')}</style> </head> <body><div id="root">{app}</div></body> </html>, { // ... })
客户端代码(client.js)
// client.js const insertCss = (...styles) => { const removeCss = styles.map(style => style._insertCss()); return () => removeCss.forEach(dispose => dispose()); }; const StyleContext = require('isomorphic-style-loader/StyleContext'); const hydratedApp = <StyleContext.Provider value={{ insertCss }}><Component /></StyleContext.Provider>; hydrateRoot(document.getElementById('root'), hydratedApp);
问题原因
renderToPipeableStream是React 18引入的流式渲染API,会边渲染组件边向客户端输出HTML内容。原代码中<head>里的<style>标签会在组件渲染前就被输出,此时css集合还未收集到组件样式,导致服务端输出的HTML无样式,客户端hydrate时才注入样式,引发页面闪屏或样式丢失。
解决方案
调整服务端代码,将收集到的样式放在<body>末尾的<style>标签中,避免流式渲染时样式未收集完成就输出;客户端可按需将样式移至<head>以符合规范:
修改后的服务端代码
// server.js const css = new Set(); const StyleContext = require('isomorphic-style-loader/StyleContext'); const insertCss = (...styles) => styles.forEach(style => css.add(style._getCss())); const app = <StyleContext.Provider value={{ insertCss }}><Component /></StyleContext.Provider>; const { pipe, abort } = ReactDomServer.renderToPipeableStream( <html> <head> {/* 保留其他头部资源 */} </head> <body> <div id="root">{app}</div> {/* 将收集到的样式插入到body末尾 */} <style id="ssr-styles">{[...css].join('')}</style> </body> </html>, { onShellReady() { // 处理响应流输出 }, onError(err) { console.error('渲染错误:', err); } } );
可选:客户端调整样式位置
如果希望样式放在<head>中,可在客户端hydrate完成后移动样式标签:
// client.js const insertCss = (...styles) => { const removeCss = styles.map(style => style._insertCss()); return () => removeCss.forEach(dispose => dispose()); }; const StyleContext = require('isomorphic-style-loader/StyleContext'); const hydratedApp = <StyleContext.Provider value={{ insertCss }}><Component /></StyleContext.Provider>; hydrateRoot(document.getElementById('root'), hydratedApp).then(() => { const ssrStyles = document.getElementById('ssr-styles'); if (ssrStyles) { document.head.appendChild(ssrStyles); } });
替代方案
如果上述调整仍无法满足需求,可考虑改用支持React 18流式SSR的样式方案,比如:
@emotion/react:原生支持流式SSR渲染styled-components:通过ServerStyleSheet配合流式API实现样式收集
内容的提问来源于stack exchange,提问作者D.H.Lolo
相关产品推荐
相关产品推荐

