The Problem
When creating new React applications they are set up for desktop being in mind when loading assets, not mobile connections. If we run a chrome Lighthouse report, we can see drastic differences between desktop performance and mobile performance.
As developers we want the same experience for our application regardless of whether you are on mobile or desktop.
Luckily this isn't that hard to implement!
What Creates the Issue
When rendering new components in React we are importing javascript file and all imports that that component may contain!
The flow looks something like this:
And the issue becomes a little clearer.
If we had a two files: index.js
and App.js
// index.js
import App from './App.js'
console.log('Hello World!')
// App.js
import React, { Component } from 'react'
import SomeExtraClass from 'some-package'
import { func1, func2, func3 } from 'another-package'
console.log('Imported Packages!')
And ended up running index.js
, we would see this output to our console:
/> "Imported Packages"
/> "Hello World!"
This is great because it brings all of our code into one useable scope for the file we imported it into, although the issue arises when we are importing code that isn't necessary at the time it is imported.
Why Does It Impact Mobile?
As we build our applications, and as react is defined, larger screen sizes will be rendering more components to the page at a time than its mobile counterpart.
This can lead to components rendering in the background and leads to technically decreased performance. Due to the user waiting to load parts of the application they don't need or will see yet.
I must note that this also impacts both desktop and mobile performance when we are building large applications with many nested components. The more we have to render to the end user, the longer the application will take to load.
So how do we go about only loading the necessary content for the page in prioritized order?
Welcome to React.lazy.
React.lazy
Luckily React has built a package that handles this for us: React.lazy
.
We can import lazy
as a named method from the react library if we would like or call to it as React.lazy()
.
React.lazy takes a function that must call a dynamic import(). This must return a Promise which resolves to a module with a default export containing a React component.
Awesome, so we can now dynamically import extra components and leave it up to React to decide when to display them!
React.lazy is currently not supported for server-side rendering.
Code-Splitting With React.lazy
Code-Splitting is a feature supported by bundlers like Webpack, Rollup and Browserify (via factor-bundle) which can create multiple bundles that can be dynamically loaded at runtime. - reactjs.org
This is what allows us to dynamically load separate files. Bundlers handle the different flows that our application will take when loading new files and creates separate bundle files that will be loaded for certain components.
This separation of concerns for runtime is what allows us to increase performance within our applications.
Suspense
Since this is loading our imports asynchronously, we don't want to wait for everything to load let's do something in the meantime for those couple of milliseconds it takes to import!
Suspense allows us to declare what should be displayed to the DOM while we wait on the rest of our components to load, until our promise resolves. Thanks React.lazy
!
How To Use React.lazy
// App.js
import React, { Component, lazy, Suspense } from 'react'
const Header = lazy( () => import('./Header.js'))
const Body = lazy( () => import('./Body.js'))
class App extends Component {
render() {
return (
<div>
<Suspense fallback={<p>Loading...</p>}>
<p>Loaded Page!</p>
<Header/>
<Body/>
</Suspense>
</div>
)
}
}
As React goes to render our App component, the lifecycle now looks like this:
The Benefits
Now that we are asynchronously loading our extra components and have a fallback function, we no longer have to wait for components to render. As we can rely on the fallback function to handle our first contentful paint, and wait for them to load through React.lazy
and then update our component!
This may only reduce loading times by a couple of milliseconds but it is better to display something like Loading...
or maybe a loading animation, rather than having a blank component and then allow React to come back in and update the component once its ready to render!
Profiling With Lighthouse
To prove that React.lazy
actually works let's profile it with Lighthouse using a basic application. You can find the example at this repository if you would like to test it on your own machine.
Desktop
No Suspense
With Suspense
Mobile
No Suspense
Suspense
Conclusion
By using React.lazy
and the Suspense
component, we were able to drastically improve our websites loading performance by supplying a fallback render method.
This will handle rendering until our component can actually be rendered to the user.
Mobile performance sees a much larger gain rather than Desktop performance, although both are increased!
Top comments (0)