SSR: dynamic import in react app how to deal with

2020-04-07 04:08发布

问题:

I'm just starting on server side rendering a react 16 app using code splitting and dynamic import thanks to webpack 4 and react-loadable.

My question might sound stupid but there's something I don't quite get.

On the server side, I'm waiting that webpack has loaded all modules before spitting out the html to the client.

On the client side I have a kind of loading component rendered, before rendering the loaded component.

So basically the server renders the loaded component:

<div>loaded component</div>

And the client hydrates the loading component:

<div>loading...</div>

Obviously, The problem is that React complains after hydrate() because there is a miss match between server and client.

During a few seconds the client first renders

<div>loading...</div>

whereas server has rendered and sent to the client, the html of the loaded component.

Can someone enlighten me ? how does it work exactly ? How can I prevent a mismatch at first render when the component is being loaded ?

回答1:

Looks like you're not preloading the assents in you client.

Loadable.preloadReady().then(() => {
  ReactDOM.hydrate(<App/>, document.getElementById('app'));
});

This is also a required step to avoid the hydration mismatch.

Reason:

This issue is caused on your client because the initial request, your chunks were not loaded, so the html output for those components would be loading... instead of the component content itself. Only after the chunks are fetched and loaded that this initial state loading... will be replaced by the desired content.

So, Loadable.preloadReady method creates a Promise that will be resolved once the application chunks were preloaded, in that way, having all assets needed for the initial stage, ReactDOM.hydrate will generate the same output as your server did.


TIP

Also I recommend you to take a look at React Loadable SSR Add-on, it is a very handy add-on that will enhance your server side assets management, giving you the same benefits as it was CSR (Client Side Render).

Server Side Render add-on for React Loadable. Load splitted chunks was never that easy.

See https://github.com/themgoncalves/react-loadable-ssr-addon



回答2:

Actually it is very simple even if React.lazy/Suspense are not supported yet.

That's how i managed it to work:

import React, { lazy, Suspense, useEffect, useState } from 'react';

export default function App() {
  // this is my dynamically loaded component
  let Dashboard = lazy(() => import(/* webpackChunkName: "Dashboard", webpackPreload: true */ '../Dashboard/Dashboard'));

  // I use hooks to determine if ssr is done and i'm on the browser
  const [isBrowser, setIsBrowser] = useState(false);
  useEffect(() => {
    // this is like componentDidMount
    setIsBrowser(true);
  }, []);

  return (
    <section className="App">
      <header>This is App!</header>
      <main>
        {isBrowser ? (
          // if i'm in the browser i use Suspence to wait for my component
          <Suspense fallback={'Loading...'}>
            <Dashboard></Dashboard>
          </Suspense>
        ) : (
          // else i just show a loading label
          'Loading...'
        )}
      </main>
    </section>
  );
}

Note that using state and componentDidMount (or the equivalent hooks like I did) is very important to prevent html mismatch on ReactDOM.hydrate