25.33% Reduction in First Load JS with NextJS Dynamic Imports
A play by play in successfully reducing first load JS bundle size by 25% with code splitting via dynamic imports, and getting all NextJS pages into the green zone.
NextJS is nice for so many reasons, and you'd be doing yourself a disservice by not using it in 2021
for web development. But that's besides the point of this post.
My goal with this post is to document my own personal success story and exploratory efforts with NextJS
and code splitting for improving page load time. Hopefully this will be both high-level enough to communicate benefits to say,
a hands-off engineering manager or a backend developer, and in-depth enough to point a frontend developer in the right
direction if they were to explore this area themselves.
next build creates an optimized production
build of your application. The output displays information about each route.
The first load is colored green, yellow, or red. Aim for green for performant
When you use Next CLI, this little bit of feedback on its own is immensely valueable for
monitoring your own bundle size. Every time you build your application, the NextJS CLI outputs
your First Load JS for each of your pages. From this, you can get a rough estimate of how well you're doing
in terms of page speed.
I'm unable to replicate the CLI output colors exactly as they would appear, so
I'm substituting them with green/yellow squares here: 🟩 / 🟨
Here's my initial next build output.
Page Size First Load JS
┌ ● / 3.71 kB 128 kB 🟩
├ /_app 0 B 79.2 kB 🟩
├ ● /[year]/[month]/[day]/[slug] 8.9 kB 133 kB 🟨
├ └ css/57d6f1c73619f1a44c66.css 3.69 kB
├ ├ /2021/03/01/infrastructure-as-code-to-save-time
├ ├ /2021/03/06/jamstack-ci-cd-with-lerna-next-js-cdk-and-github-actions
├ └ /2021/03/15/reduce-first-load-js
├ ○ /404 1.22 kB 122 kB 🟩
├ ● /how-to 3.83 kB 128 kB 🟩
└ ○ /me 5.17 kB 126 kB 🟩
└ css/bcc0ab3f4a6e0a5d3b14.css 960 B
+ First Load JS shared by all 79.2 kB 🟩
├ chunks/12ba7591d43e0be4bd8f2d0b114e9dee332ebc4c.c6c005.js 12.7 kB
├ chunks/commons.894139.js 15.4 kB
├ chunks/framework.e25a57.js 42.3 kB
├ chunks/main.e88387.js 6.59 kB
├ chunks/pages/_app.9c4434.js 658 B
├ chunks/webpack.648aa6.js 1.58 kB
└ css/5bddb30097b44559415a.css 3.81 kB
λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
○ (Static) automatically rendered as static HTML (uses no initial props)
● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)
(ISR) incremental static regeneration (uses revalidate in getStaticProps)
The one highlighted line is what triggered onset of this article.
The first thing I noticed was 3rd party components that I was pulling in from @material-ui into
a nested Header component. I made yet another guess that replacing those with homemade components could shave down on some
Warning: Replacing a full-fledged, battle-tested, whatever-you-want-to-call-it solution
from a large open source library, such as Material UI, could potentially be a
large undertaking — there's a reason why those solutions exist. The complexity
will vary on a case-by-case basis, and it's possible that the speed
if any at all, may not actually be worth the engineering effort.
- import AppBar from "@material-ui/core/AppBar";- import Toolbar from "@material-ui/core/Toolbar";+ // ...Own implementation for AppBar+ // ...Own implementation for ToolBar import useScrollTrigger from "@material-ui/core/useScrollTrigger";
import useMediaQuery from "@material-ui/core/useMediaQuery";
Without diving into specifics, this particular component refactor happened to be a light lift — it took about
5 minutes — but it didn't result in significant improvements to first load JS.
Assuming my before-and-after comparisons makes sense, here are my conclusions.
Success: The average first load JS decreased by 17.87%,
from 119.36 kB to 98.03 kB
Success: The first load JS for the largest page — /[year]/[month]/[day]/[slug], which
is also a dynamic route for N-number of statically generated pages — decreased by 25.33%, from 133 kB to 99.3 kB
This brought all the pages into NextJS's green zone. 🎉
And for fun, here's a shot of the in-browser lighthouse score. (Although https://web.dev/measure/
is a more accurate assessment.)
So, why is this good? Faster page load times means better lighthouse scores which means better search engine ranking
which means more traffic which means greater potential for new customer acquisition (if you're a business) which means more
money! (Just assumptions here.)