javascriptreactjswebpackserver-side-renderingreact-loadable

React SSR blinks page


I created a project with React, react-router, @loadable/component.

Now I'm trying to add SSR to this project. I did server side rendering with react-router.

And then I added @loadable/component to import all pages component:

import loadable from '@loadable/component';

const routersConfig = [
  {
    path: '/',
    component: loadable(() => import('./Home')),
    exact: true,
  },
  {
    path: '/2',
    component: loadable(() => import('./Home2')),
    exact: true,
  },
];

Then I added all this parts of code: https://www.smooth-code.com/open-source/loadable-components/docs/server-side-rendering/

And now it works. But It works with the problem: a content blinks while loading.

How I understand the page's loading process:

  1. Browser gets a content generated by SSR (the first query in network tab)
  2. Browser renders a content (with left and top margins )
  3. Browser downloads two enterpoints and vendors from html (app.js, normalizer.js, vendor.js)
  4. Browser executes app.js and normalizer.js. Left and top margins are removed.
  5. App.js starts downloading page's chunk - home.js. In this moment content disappears
  6. When home.js is downloaded, the content appears again.

I shoot a video to illustrate this process. (I'm sorry for quality, stackoverflow forbides files which size is more then 2MB ). I'm throttling network speed to imagine all page's download process.

enter image description here

My question is why the content disappears? How to fix it?

My code

server.js

const sheetStyledComponents = new ServerStyleSheet();
  const sheetsJssRegistry = createSheetsRegistry();
  const statsFile = path.resolve(process.cwd(), './build-ssr/dist/loadable-stats.json');

  const extractor = new ChunkExtractor({
    statsFile,
    entrypoints: [
      'app',
      'normalize',
    ],
  });


  try {
    const client = ApolloSSRClient();

    const tree = (
      <ApolloProvider client={client}>
        <ApplyTheme sheetsRegistry={sheetsJssRegistry}>
          <StaticRouter location={req.url}>
            <Home />
          </StaticRouter>
        </ApplyTheme>
      </ApolloProvider>
    );

// there is combination of Apollo graphql, jss, styledComponent functions
    const body = await getMarkupFromTree({
      renderFunction: flow(
        sheetStyledComponents.collectStyles.bind(sheetStyledComponents),
        extractor.collectChunks.bind(extractor),
        renderToString
      ),
      tree,
    });

    const scriptTags = extractor.getScriptTags(); 
    // It isn't used yet
    const linkTags = extractor.getLinkTags(); 

    const styleTags = sheetStyledComponents.getStyleTags();

    const html = (await rawHtml)
      .replace(
        '</head>',
        ` 
          ${styleTags}
          <style type="text/css" id='jss-server-side-styles'>
              ${sheetsJssRegistry.toString()}
          </style>
          <script>
            window.__APOLLO_STATE__ = ${JSON.stringify(client.extract())};
          </script>
          ${scriptTags}
          </head>
        `
      )
      .replace('<div id="app"></div>', `<div id="app">${body}</div>`);


    res.send(html);

index.jsx

const SSRApp = (
  <ApolloProvider client={ApolloClient}>
    <ApplyTheme>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </ApplyTheme>
  </ApolloProvider>
);

loadableReady(() => (
  ReactDOM.hydrate(
    SSRApp,
    document.getElementById('app'),
  )
));

Solution

  • It was my fault.

    The hydration version of app contained BrowserRouter -> Switch -> Router -> HomePage

    And the SSR version contained only StaticRouter -> HomePage

    Because of this, after rendering SSR version, react removed all DOM and created new one with Router.