How to optimize React.js app load time using react…
Often React.js applications could grow larger and could be taking more time to load the content. React team offered a very useful technique to lazily load the bundled content without bundling it all together with the main bundle. This will optimize the load time and show the content without taking much time to load the content fully.
What is React.lazy?
Before all of these lazy loading and more involvement with package bundlers, the way to optimize load time was to import the code splitted into two or more by using dependencies or injections. So initial content is loaded with a light weight code bundle and other heavy parts are eventually loaded after the web page is rendered.
When ECMA proposed the method of dynamic imports, it was a very useful method to adhere code splitting without worrying about what to split and dependencies throughout since we can check its functionality even in dev mode. https://github.com/tc39/proposal-dynamic-import this link has a proposal of its usage with dynamic import syntax.
Package bundlers like Webpack most willingly supported this and this lead to many code splitting and dynamic loading concepts for frameworks like React, Angular and Vue.js
So what is React.lazy? Simply its a way of loading content dynamically to render without blocking the rendering until all the content is loaded. You can declare it as follows.
// load content dynamically const ProfileComponent = React.lazy(() => import('./ProfileComponent'));
So to use this there are few important steps in React.js. Mainly we will answer;
- How code is splitted when you use React.js
- Showing a loading component when the dynamic content is downloading.
- Measing optimization using lighthouse
How to split your code using React.lazy and optimize React.js load time
For this we will use a simple example to demonstrate how to use dynamic loading.
This will load the examples from chart.js which is a common example to load stock data or reports. Lets look at the code on how to load them
import React, {Suspense} from 'react'; import Loader from "react-loader-spinner"; import Line from './components/Line'; import './App.css'; const App = () => { return ( <div className="App"> <div className="Col"> <StockChart /> </div> <div className="Col"> <Line /> </div> </div> ); } export default App;
When we build this example we can see the following js bundles in the build folder.
Now we will change the StockChart
into dynamic loading, here we need to use the React.Suspense
component to wrap the component until the data is loaded.
import React, {Suspense} from 'react'; import Loader from "react-loader-spinner"; import Line from './components/Line'; import './App.css'; const StockChart = React.lazy(() => import('./components/StockChart')); const App = () => { return ( <div className="App"> <div className="Col"> <Suspense fallback={<Loader type="Bars" color="#00BFFF" height={80} width={80} />}> <StockChart /> </Suspense> </div> <div className="Col"> <Line /> </div> </div> ); } export default App;
Now you can see an extra bundle when you run yarn build
to produce the bundled code using webpack. This is the dynamic loading component that will be loaded over the network once the content is loaded
To demonstrate how the loading is visualized I will add a loading animation to the chart component and visualize it with a timeout which is equivalent to loading a chunk parallelly through a network.
import React, {Suspense} from 'react'; import Loader from "react-loader-spinner"; import Line from './components/Line'; import './App.css'; const StockChart = React.lazy(() => { return new Promise((res) => { setTimeout(() => { res(import('./components/StockChart')); }, 5000); }) }); const App = () => { return ( <div className="App"> <div className="Col"> <Suspense fallback={<Loader type="Bars" color="#00BFFF" height={80} width={80} />}> <StockChart /> </Suspense> </div> <div className="Col"> <Line /> </div> </div> ); } export default App;
Now you can see that the loader is shown when the component is loading. Any component that you add in suspense will load until the component is loaded over the network.
<Suspense fallback={<Loader type="Bars" color="#00BFFF" height={80} width={80} />}> <StockChart /> </Suspense>
How to handle the error when loading the component
Sometimes there could be errors when loading suspense components due to network conditions or many different reasons. The following code demonstrates how to handle it within the parent component just as Suspense component.
import React, {Suspense} from 'react'; import Loader from "react-loader-spinner"; import Line from './components/Line'; import './App.css'; const StockChart = React.lazy(() => { return new Promise((res, rej) => { setTimeout(() => { rej(); }, 2000); }) }); const App = () => { return ( <div className="App"> <div className="Col"> <ErrorBoundary fallback={<div>Error loading component</div>}> <Suspense fallback={<Loader type="Bars" color="#00BFFF" height={80} width={80} />}> <StockChart /> </Suspense> </ErrorBoundary> </div> <div className="Col"> <Line /> </div> </div> ); } class ErrorBoundary extends React.Component { state = { hasError: false, error: null }; static getDerivedStateFromError(error) { return { hasError: true, error }; } render() { if (this.state.hasError) { return this.props.fallback; } return this.props.children; } } export default App;
Note that there is no hook to catch these errors within the component. So we will have to use the class as shown in the code.
How to measure the load time opitimization
You can use Lighthouse plugin with chrome to create a report of load time and other measurements to see how your website is performing.
First without the suspense.
Next when we add the suspense for both the Line and StockChart components.
You can see that the contentful paint took has a 0.1s optimization with lighthouse score criteria. This could be optimizing much more when you have a large content to load.
I think you got an overall good understanding of how to optimize React.js load time using dynamic loading in react. Feel free to leave a comment if you need more information.