NERDJACKING
Published in

NERDJACKING

Lazy load on React SSR with Code Splitting

How I automatised assets loading and make my SSR feel like CSR.

What you need to know before

For the purpose of this article, we assume that you already have basic understanding about server side rendering with React and of course, basic notion of Webpack.

But if you don’t, there’s no harm as well, just have fun 😄

Everything is a trade-off!

Or at least used to be…

So it all started because of the famous issue about Single Page Application harm SEO, which is true, sadly!

And to make that right, the solution was to put the SPA into SSR (Server Side Render) mode.

Just after making the first runs on the app, I realised that I just had lost the most awesome benefit from the CSR (Client Side Render): assets lazy loading.

This simply meant that I had to discard the code spliting and work purely with big bundles, or simply find another way to do it.

And I’m glad I did find another way!

It is not a big deal if you work with a single ugly bundle, in that way, you don’t have to care about dependency discovery, all you need to do is to place the single script inside the html body.

<script type="text/javascript" src="./app.js" />

Example of a single big ugly bundle placement.

otherwise if you’re working with Webpack Split Chunks you will have a bad time discovering the assets dependencies and loading it by demand (lazy loading) in the server side…

module.exports = {
output: {
// ...
filename: '[name].bundle.js',
chunkFilename: '[name].chunk.js',
},
output: {
// ...
},
optimization: {
splitChunks: {
name: false,
cacheGroups: {
vendors: {
name: 'vendors',
test: /[\\/]node_modules[\\/]/,
chunks: 'all',
minChunks: 1,
priority: -10,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
plugins: [
// ...
],
};

Webpack Split Chunks configuration example

So what is the big deal?

Webpack Split Chunks is a great strategy to optimise your application. It will split your big ugly bundle into smart and reusable chunks that will be loaded by demand (a.k.a. lazy loading).

It means that, instead of have one big bundle weighting over 2mb, you could break it into several smaller pieces (chunks) weighting ~100kb instead, and that will be only loaded when it is necessary!

No more bandwidth waste!

Just think: it is simply amazing for mobile users! 😛

React-Loadable chunks representation: a single giant bundle vs. multiple smaller bundles

And that’s the big deal: know which assets and its dependencies we need to load on each request.

Enough talk, let’s work!

Here is how I made my React SSR feel like SSR:

What are we going to do?

  1. Create an express sever to render and serve the react application as html;
  2. Create an browser client to handle the react application;
  3. Use react-loadable for code splitting;
  4. And for the server, use react-loadable-ssr-addon to identify and load automatically the assets dependencies.

The application structure

For the purpose of this article, we will create a very basic structure just to demonstrate the assets loading behaviour.

But if you do want to use a strong pattern, I recommend [React] Adventure Pattern Proposal 😜

root
├── source
| ├── components # components that we'll load dynamically
│ │ ├── title.jsx
│ │ ├── loremIpsum.jsx
│ │ ├── app.jsx
│ ├── client.jsx # client entry point (for browser)
│ └── server.jsx # server configurations
├── .babelrc # babel configuration for server.js
├── webpack.config.js # webpack configurations
├── package.json

Creating a new package.json file

Open your terminal or console, and run the following command in the root of the project:

$ npm init

After you finish the npm-init set-up, open the package.json file you just created and replace the scripts tag for the following one:

"scripts": {
"start": "npm run clean && npm run build && babel-node source/server.jsx",
"build": "webpack",
"clean": "rm -rf ./source/dist/"
},

With the configuration above, we defined the commands available for our project.

  1. clean: will delete the build directory for a fresh and clean start.
  2. build: trigger webpack’s build pipeline.
  3. start: triggers the command above and start the express server.

Webpack configuration

Installing

First, we need to install Webpack and Babel packages with its plugins.

$ yarn add -D webpack webpack-cli @babel/cli @babel/node @babel/core @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread @babel/plugin-syntax-dynamic-import babel-plugin-dynamic-import-node babel-loader

Then, we need to install react-loadable-ssr-addon *the one that makes the magic happens* to handle the application assets and react-loadable to dynamically load components and handle code splitting.

$ yarn add react-loadable react-loadable-ssr-addon

Note that the packages above should NOT be placed in the devDependencies.

Configuring

Webpack basic configuration for splitted chunks

Note that the webpack.config.js file should be placed in the root of the project.

Understanding the magic

new ReactLoadableSSRAddon({
filename: 'react-loadable-ssr-addon.json',
}),

Inside Webpack’s plugins option, we defined a new instance of ReactLoadableSSRAddon, that will track all assets dependencies in our application.

ReactLoadableSSRAddon will then, generate an manifest file that will be used by the server to discover, load and inject the assets in the html output.

In that way, we got back one big benefit from the CSR: the automatically placement of assets inside the html.

The React Application

At this point we need to install react and react-dom libraries.

$ yarn add react react-dom

Creating asynchronous components.

With react-loadable we can take the advantage of the dynamic import and create an asynchronous component that will be loaded when it became required.

Also, it will be generate as a chunk file by Webpack, thanks to the output.chunkFilename configuration.

  1. source/app.jsx
source/components/app.jsx

2. source/components/loremIpsum.jsx

source/components/loremIpsum.jsx

3. source/components/title.jsx

source/components/title.jsx

Client (browser) set-up

  1. source/client.jsx
source/client.jsx

Server set-up

Installing

In this step, we need to install Express, a powerfull HTTP server for Node.js

$ yarn add -D express

Configuring

  1. Creating .babelrc server configuration

Note that the .babelrc file should be placed in the root of the project.

2. source/server.jsx

Understanding the magic

Once the express server gets a new request, it will render the application withrenderToString(), and during this process, with<Loadable.Caputre />, will return the chunks’ module name that we required.

The module’s name will be store inside ES6 Set() and after that, passed to the getBundles() method, from ReactLoadableSSRAddon, together with the assets manifest generated by its Webpack plugin.

With that, it will map all assets dependencies and returns an object with the assets to be loaded, grouped by its file type.

import { getBundles } from 'react-loadable-ssr-addon';

/**
* getBundles
*
@param {object} manifest - The assets manifest content generate by ReactLoadableSSRAddon
*
@param {array} chunks - Chunks list to be loaded
*
@returns {array} - Assets list group by file type
*/
const
bundles = getBundles(manifest, modules);
// example of bundles result
const styles = bundles.css || [];
const scripts = bundles.js || [];
const xml = bundles.xml || [];
const json = bundles.json || [];

Then, the only thing you need to do is to iterate over the object and place it inside the html output.

${scripts.map(script => {
return `<script src="/dist/${script.file}"></script>`
}).join('\n')}

Starting the application

Open you terminal/console application and run the following command:

$ yarn start

Once it’s ready, you should see the following message:

> Running on http://localhost:3003/

And that’s it! Just open your browser and type the address above to check it out!

* Bonus!

Working with Subresource Integrity (SRI)

According to MDN documentation, Subresouce Integrity (SRI) is a security feature that enables browsers to verify that resources they fetch (for example, from a CDN) are delivered without unexpected manipulation. It works by allowing you to provide a cryptographic hash that a fetched resource must match.

<script src="lorem-ipsun.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossOrigin="anonymous"></script>

Example of SRI implementation

With ReactLoadableSSRAddon, you can turn on this feature just by updating its Webpack plugin configuration:

new ReactLoadableSSRAddon({
filename: 'assets-manifest.json',
integrity: true,
integrityAlgorithms: [ 'sha256', 'sha384', 'sha512' ],
integrityPropertyName: 'integrity',
})

Then, it will be available as a key for you:

${scripts.map(script => {
return `<script src="/dist/${script.file}" integrity="${integrity}" crossOrigin="anonymous"></script>`
}).join('\n')}

And that’s all folks 🎉🎉🎉

Have anything so say? Write in the comments 📣

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store