20

I am trying to create an ssr react app with lazy loading import. Everything works fine except it does not fetch all the required chunks.

I am also wondering if this is related to a dynamic component, which is base on server response

Edit - It actually renders all the required chunks but it wipes out the whole thing when client-side takes over and renders again

Since it re-renders all it slows down by a lot.

enter image description here

The parser happens on server-side and when client-side takes over it fetches more server.js

 const history = createHistory({
    initialEntries: [urlPath],
  })
  // const history = createHistory()
  const store = configureStore(history, {
    location: {
        ...
    },

  })

  const context = {}
  const htmlRoot = (
    <Provider store={store}>
      <StaticRouter location={urlPath} context={context}>
        <AppRoot />
      </StaticRouter>
    </Provider>
  )

  // pre fetching data from api
  store
    .runSaga(rootSaga)
    .done.then(() => {

        const RTS = renderToString(htmlRoot) + printDrainHydrateMarks()
        const head = Helmet.renderStatic() 
        console.log(printDrainHydrateMarks())

        res.status(code).send(renderDom(RTS, port, host, storeState, head))
      }
    })
    .catch(e => {
      console.log(e.message)
      res.status(500).send(e.message)
    })

  renderToString(htmlRoot)
  console.log(printDrainHydrateMarks())

  store.close()
} else {
  res.status(500).send(_err)
}

Prod Server

Loadable.preloadAll().then(() => {
  app.listen(PROD_PORT, (error) => {

  })
});

Client side

Loadable.preloadReady().then(() => {
    hydrate(
      <Provider store={store}>
        <ConnectedRouter history={history}>
          <AppRoot />
        </ConnectedRouter>
      </Provider>,
      domRoot,
    )
  })

Split Chunks setup

    styles: {
      name: 'styles',
      test: /\.css$/,
      chunks: 'all',
      enforce: true
    },

Any opinion or advice is welcome please

Someone suggested to try with window.onload = () => { but this approach seems slow down as well.

Arya McCarthy
  • 8,554
  • 4
  • 34
  • 56
fiddlest
  • 1,262
  • 2
  • 17
  • 42

4 Answers4

2

You should use ReactLoadablePlugin to have a list of loadable imports:

new ReactLoadablePlugin({
  filename: './build/react-loadable.json',
})

Using "react loadable capture", you can find out which dynamic components are needed while rendering and you can add their bundles to your header file:

const content = ReactDOMServer.renderToString(
  <Loadable.Capture report={moduleName => modules.push(moduleName)}>
    <Provider store={configureStore()}>
      <StaticRouter location={req.url} context={context}>
        <App />
      </StaticRouter>

    </Provider>
  </Loadable.Capture>
);

  let bundles = getBundles(stats, modules);
  bundles.map((item) => {
      //add js to header
      })

This prevents react loadable from wiping out the content and rerendering it.

To config the Webpack to output right chunk based on dynamically loaded component do this:

 optimization: {
    splitChunks: {
      cacheGroups: {
          default: false,
          vendors: false,
      }
  }
}

This configuration works for me in Webpack v4.

You can find a complete documentation to work with React loadable server-side rendering here:

https://github.com/jamiebuilds/react-loadable

Hadi Ranjbar
  • 1,692
  • 4
  • 21
  • 44
0

I am using route-centric code splitting with react-universal-component and flush chuck. Sorry for the long code snippet/pseudo code, I have tried my best to make it shorter. :)

First build an array for react router mapping corresponding components

let routeConfig = [
  {path: '/foo', component: 'Foo'},
  {path: '/bar', component: 'Bar'}
];

universal function make sure the component and its children can be correctly imported on both server side and client side. It's smart enough to lazy load the split codes accordingly.

import universal from 'react-universal-component';
const routes = routeConfig.map((item) =>{
  let route = {};
  route.path = item.path;
  route.component = universal(import(`./container/${item.component}`), options);
  return route;
});

Render the router in React Component.

class App extends React.Component {
 render() {
    return (
      <div>
        <Switch>
          {routes.map( route => <Route key={ route.path } { ...route } />)}
        </Switch>
      </div>
);}}

Config webpack define the code splitting names.

  output: {
    filename: '[name].js',
    chunkFilename: '[name].js',
    path: path.resolve(__dirname, '../dist/client'),
    publicPath: '/xxxxxx/'
  },
  plugins: [
    new MiniCssExtractPlugin({
      // Options similar to the same options in webpackOptions.output
      // both options are optional
      filename: '[name].css',
      chunkFilename: '[id].css'
  })],
  optimization: {
    splitChunks: {
      chunks: 'initial',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/, 
          name: 'vendor' // bundle all the npm module as vendor.js
}}}}

Finally, the server.js will use flush chuck to do the code splitting.

const chunkNames = flushChunkNames();
const {js, styles, cssHash, scripts, stylesheets} = flushChunks(clientStats, {chunkNames});
res.send(`<!doctype html>
  <html>
    <head>
      ${styles}
    </head>
    <body>
      <div id="root">${app}</div>
      ${cssHash}
      ${js}
    </body>
  </html>`);
Eric Tan
  • 1,377
  • 15
  • 14
0

You should consider using ReactLoadableSSRAddon, it works better than the plugin provided by ReactLoadable see this link for further info. In my case it made a huge difference !

jav974
  • 1,022
  • 10
  • 22
0

if you are using ts-loader try setting:

tsconfig.json

{
  .
  .
  .

  "module" : "esnext",
  "moduleResolution": "Node",
  .
  .
  .
}
armin yahya
  • 1,368
  • 7
  • 14