razzle icon indicating copy to clipboard operation
razzle copied to clipboard

SSR + suspense + lazy + server components

Open laurencefass opened this issue 3 years ago • 4 comments

Is there a roadmap for Razzle integration of React 18 features: SSR + suspense + lazy + server components? Looks like the future of SSR. This is the best starting point for i've found up-to-date info and discussion:

https://github.com/reactwg/react-18/discussions/37

The hot new API looks like renderToPipeableStream....

Thanks!

laurencefass avatar Jan 08 '22 18:01 laurencefass

No roadmap but razzle5 is aiming for this.

fivethreeo avatar Jan 09 '22 16:01 fivethreeo

While you are waiting for Razzle 5, I'd just say you can try that new hot API now

export const renderApp = (req, res) => {
  const context = {};
  let didError = false;
  const { pipe, abort } = renderToPipeableStream(
    <Html assets={assets}>
       <StaticRouter location={req.url} context={context}>
          <App />
       </StaticRouter>
    </Html>,
    {
      onShellError(x) {
        didError = true;
        console.error(x);
      },
      onShellReady() {
        res.statusCode = didError ? 500 : 200; //handle the error however you'd like to
        res.setHeader("Content-type", "text/html");
        res.write('<!DOCTYPE html>');
        pipe(res);
      }
    }
  );
  setTimeout(abort, 60000); //abandon if it takes too long
}

then

function handleErrors(fn) {
  return async function (req, res, next) {
    try {
      return await fn(req, res);
    }
    catch (x) {
      next(x);
    }
  };
}

and

//const server = express();
server.get('/*', handleErrors(async function (req, res) {
  renderApp(req, res);
}));

and create a Html.jsx file like this

export default function Html(props) {
    const { assets, children } = props;
    const cssLinksFromAssets = (assets, entrypoint) => {
        return assets ? assets[entrypoint] ? assets[entrypoint].css ?
            assets[entrypoint].css.map(asset =>
                <link rel="stylesheet" href={`${asset}`} />
            ) : '' : '' : ""
    };
    const jsScriptTagsFromAssets = (assets, entrypoint, ...extra) => {
        return assets ? assets[entrypoint] ? assets[entrypoint].js ?
            assets[entrypoint].js.map(asset =>
                <script src={`${asset} ${extra.join(" ")}`}></script>
            ) : '' : '' : '';
    };
    return (
        <html>
            <head>
                <meta charSet="utf-8" />
                <meta name="viewport" content="width=device-width, initial-scale=1.0" />
                <meta name="theme-color" content="#000000" />
                <link rel="apple-touch-icon" href="/logo192.png" />
                <link rel="manifest" href="/manifest.json" />
                <link rel="icon" href="/img/favicon.png" />
                {cssLinksFromAssets(assets, 'client')}
            </head> {/*You can also use res.write(`<html><head>...${cssLinksFromAssets(assets, 'client')}</head><body>`) before pipe(res) and after pipe(res) add res.write(`${jsScript...}</body></html>`). This way, you can use Razzle's default jsScriptTagsFromAssets and cssLinksFromAssets, idk if it's a bad practice tho*/}
            <body>
                <div id="root">{children}</div>
                {jsScriptTagsFromAssets(assets, 'client', '', '')}
            </body>
        </html>
    );
};

Just saying that this worked for me

DuCanhGH avatar Jan 14 '22 17:01 DuCanhGH

"idk if it's a bad practice tho"

I think this depends on whether you're rendering/hydrating the whole document or only the div id="root" container node. If it's the whole document, then I believe it needs to be all as react components. If it's the container node, you can probably use res.write().

therealgilles avatar Apr 05 '22 05:04 therealgilles

@therealgilles yeah, right, thanks :)

DuCanhGH avatar Apr 05 '22 10:04 DuCanhGH