React 18 -> RenderToString and @loadable/server creating hydration issue

52 Views Asked by At

We are trying to migrate to react 18. Our current code uses React 17 with @loadable for chunking at server and client with renderToString. On simple migration to React 18 gives hydration error even on rendering just a div.

On researching, I found these two things,

  1. React 18 introduced streaming SSR, and renderToString is a blocking operation. This means it won't capture loadable components that haven't been loaded yet.
  2. @loadable issue --> https://github.com/gregberge/loadable-components/issues/718

We would love to migrate to renderToPipeableStream but it will take sometime but if anyone can help with migrating to renderToPipepableStream with/without loadable or making renderToString work, that would be great. For reference Attaching code snippet for sever and client side below

SERVER

let appContent = '';
    let headCss = '';
    const statsFile = path.resolve(cwd, clientOutput, 'loadable-stats.json');
    const extractor = new ChunkExtractor({statsFile, entrypoints: [applicationName]});

    try {
      const sheet = new ServerStyleSheet();
      const jsx = extractor.collectChunks(
        <div id="ssr_random_2">Hello World</div>;,
      );

      appContent = renderToString(sheet.collectStyles(jsx));
      console.log('After renderToString);
      styleCss = sheet.getStyleTags(); // or sheet.getStyleElement();
    } catch (err) {
      console.log('err...', err);
      shouldCache = false;
      console.log('renderToStringFailed)
    }

    const helmet = Helmet.renderStatic();
    const helmetObj = {
      htmlAttributes: helmet.htmlAttributes.toString(),
      title: helmet.title.toString(),
      meta: helmet.meta.toString(),
      link: helmet.link.toString(),
    };

    const buildScriptTags = arr =>
      arr
        .map(({props}) => {
          if (
            props.id === '__LOADABLE_REQUIRED_CHUNKS__' ||
            props.id === '__LOADABLE_REQUIRED_CHUNKS___ext'
          ) {
            return `<script id=${props.id} type="application/json">${props.dangerouslySetInnerHTML.__html}</script>`;
          } else {
            return `<script ${Object.entries(props)
              .map(([key, val]) => {
                return key === 'async' ? 'defer' : `${key}="${val}"`;
              })
              .join(' ')}></script>`;
          }
        })
        .join('\n');

    const scriptTags = buildScriptTags(extractor.getScriptElements());

    const now = new Date();
    const cachedAt = now.toLocaleString();
    const data = {
      appContent,
      styleCss,
      storeJson: store.getState(),
      helmetObj,
      cachedAt,
      responseStatus,
      scriptTags,
    };
    response.status(responseStatus);

    let htmlTemplate = serverResponseTemplate(
      {...data, assets, publicPath}
    );
    console.log('After htmlTemplate initialization');
    response.send(htmlTemplate);

ServerResponseTemplate

export default function serverResponseTemplate(
  {
    appContent = '',
    styleCss = '',
    storeJson = {},
    helmetObj,
    cachedAt,
    assets,
    publicPath,
    scriptTags,
  }) {
  return `
    <!DOCTYPE html>
    <html lang="en" ${helmetObj.htmlAttributes}>
      <head>
        <script>
          var starttime = new Date();
        </script>
        ${helmetObj.link || ''}
        ${styleCss}
        ${assets
          .map(asset => {
            let assetName = asset && asset.name ? asset.name : asset;
            if (assetName.endsWith('.css')) {
              return `<link href="${publicPath}${assetName}" rel="stylesheet" type="text/css">`;
            }
            return '';
          })
          .join('')}
        ${scriptTags}
      </head>
      <body>
        <div id="root">${appContent}</div>
        <script charSet="UTF-8">
          window.cachedAt="${cachedAt}";
        </script>
      </body>
    </html>
  `;
}

Client

const domNode = document.getElementById('root');

  loadableReady(() => {
    hydrateRoot(domNode, <div id="ssr_random_2">Hello World</div>, {
      onRecoverableError,
    });
  });

0

There are 0 best solutions below