Code splitting acts on the principle that loading less code will speed up your app. Say for example that we’re building a complex dashboard application that includes the venerable d3 library for graphing data. Your users start complaining because it takes too long to load the login screen.
So, considering that performance problems can be resolved by less code, how can we solve this one? Well, do we really need to have that code for the chart when the user loads the login screen? Nope! We could load that on-demand.
Luckily for us, there’s a built-in way to do this with JavaScript standards. It’s called a dynamic import and the syntax looks like this:
import('/some-module.js').then((module) => {// do stuff with the module's exports},(error) => {// there was some error loading the module...},)
📜 Learn more about dynamic imports in the browser in Super Simple Start to ESModules in the browser
To take this further, React has built-in support for loading modules as React
components. The module must have a React component as the default export, and
you have to use the <Suspense />
component to render a fallback value while
the user waits for the module to be loaded.
// smiley-face.tsxexport default function SmileyFace() {return <div>😃</div>}// app.tsximport { lazy, Suspense } from 'react'const SmileyFace = lazy(() => import('./smiley-face.tsx'))function App() {return (<div><Suspense fallback={<div>loading...</div>}><SmileyFace /></Suspense></div>)}
🦉 One great way to analyze your app to determine the need/benefit of code splitting for a certain feature/page/interaction, is to use the “Coverage” feature of the developer tools.
👨💼 Our app has a neat Globe component that shows the user where they are on the globe. Cool right? It’s super duper fun.
But one day users started complaining the app is taking too long to load. We’re using several sizeable libraries to have the really cool globe, but users only need to load it if they click the “show globe” button and loading it ahead of time makes the app load slower.
So your job as a performance professional is to load the code on-demand so the user doesn’t have to wait to see the checkbox.
For this one, you’ll need to open the solution in isolation and open the Chrome DevTools Network tab to watch the JavaScript chunks load when you click “show globe.” Your objective is to have the network load those same chunks so they’re not in the bundle to begin with.
💰 Here’s a quick tip: In the Network tab, there’s a dropdown for artificially throttling your network speed. It defaults to “Online” but you can change it to “Fast 3G”, “Slow 3G”, etc.
Also, spend a bit of time playing with the coverage feature of the dev tools.
👨💼 So it’s great that the users can get the app loaded faster, but it’s annoying
when 99% of the time the reason the users are using the app is so they can
interact with our globe. We don’t want to have to make them wait first to load
the app and then again to load the globe. Wouldn’t it be cool if we could have
globe start loading as soon as the user hovers over the checkbox? So if they
pointerOver
or focus
the <label>
for the checkbox, we should kick off a
dynamic import for the globe module.
See if you can make that work.
👨💼 By default, when a component suspends (like our lazy
component is while
we’re lazy-loading some code), React will render the fallback of our suspense
boundary immediately. Go ahead and try it now. Hover over the checkbox for long
enough for the code to load and then check it. You’ll notice even though the
code is ready to go, it still shows our fallback UI.
The reason this happens is React wants to help us avoid a flash of loading state because it doesn’t know whether our content will be ready immediately or not. So React shows the fallback immediately, then when it sees the content is already ready, it will actually wait a little bit before switching from the fallback to the real content. This is a good default, but it’s definitely not the best user experience for our situation.
So instead, we can wrap our state update setShowGlobe
in a transition so React
doesn’t go to the suspense fallback and instead we can show a pending UI if we
so decide.
This is similar to what we’ve done in the past, so if you’d like you can add
useSpinDelay
from spin-delay
to prevent pushing
the flash of loading state to later if the globe’s code isn’t quite ready yet.
Quick Links
Legal Stuff
Social Media