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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 22:50:05