Analyze Next.js bundle size step by step
Date:
1. Introduction
To have a performnant web application in Next.js, it's vital to analyze the bundle size and ensure the bundle size is acceptable. This article starts from a blank Next.js app, then adds stuff little by little, let's see the changes of the bundle size.
I focus on the front-end bundle size. While Next.js provides a Node.js backend server, the bundle size at the backend is not our interest, as the bottleneck lays on the data transferring through Internet.
In addition, I deploy the Next.js to domain https://jscoder.io with HTTPS in the production mode. This is the ultimate environment for the end users. In case you need more instructions about the deployment, check it out: A complete guide to make your first website online with HTTPS.
jscoder.io is a public repo for JS coders. When we give a specific commit of this repo, there could install some libraries while not in used, so these libraries will be not included in the bundle.
2. Analyzing Tools
First of all, before adding a library, I will go to bundlephobia to check the size. If the size is small (<10kb), I will simply add it. Some libraries like Material-UI, bundlephobia shows a 146.5 KB (minified and gzipped). However, thanks to Tree Shaking, MUI actually does not add in the full 146.5 KB to the bundle. These ones are the target libraries I will keep an eye on it.
Note: here all sizes I talked about is the number after being minified and gzipped.
I use 2 tools to analyze the bundle size in production mode.
2.1 Pingdom
Pingdom is an online tool to analyze webpage loading performance. Given an online URL https://jscoder.io and the client browser location (here I use "North America - USA - San Francisco" constitently), Pingdom reports the page size and a breakdown, and gives potential improvement points.
2.2 @next/bundle-analyzer
@next/bundle-analyzer is a local bundle analyzer to dive into the bundle and see what is inside. Simply import this library and follow the instructions. Run ANALYZE=true yarn build
, it will build a production bundle and open your browers automatically to show a local HTML page .../.next/analyze/client.html
. It looks like:
Hover your mouse on the boxes there, it will show the detailed sizes of that package.
In case you are curious like me about the duplicate appereance of
react-dom.production.min.js
, check out this GitHub issue: 2 copies of react-dom.production.min.js with app router? In short, one of them is not bundled into the client-side package.
Note: this bundler analyzer shows ALL JavaScript packages to be included, but for specific pages, they might include only some of them.
3. Bare blank Next.js (with Tailwind)
I started from creating a new Next.js app via npx create-next-app@latest
. The I did some trivial changes to make it serve as the least bundle:
Include Google font Outfit, instead of default Inner
Reorganize the folder structure to support i18n:
src/app/[lang]/
...Modify file
middleware.ts
to support i18nReplace favicon.ico with a smaller simple image.
Add global simple pages
error.tsx
andnot-found.tsx
.Add file
colors.js
to define theme colors for consistent theme definition between MUI and Tailwind.
The changes above changed the bundle size trivially. I assume this is the minimal bundle size we would have if we go with Next.js. Here I use TypeScript and Tailwind. If you do not want to use them, the size could be a bit smaller.
Build it, deploy it, and check Pingdom, here is the result:
There are two big ones: JavaScript files (96.9 KB) and Font (33.1 KB).
The bundle analyzer shows breakdown of JS:
react-dom.production.min.js
(52.13 KB): this is the essential React-dom implementation. Next.js goes with React, so this is the backbone.static/chunks/69-***.js
(31.07 KB): this Js file includes next.js implementations includingrouter.js
,utils.js
,web-vitals
, etc.
So looks like the ~100kb JavaScript is the minimal cost of using Next.js.
Up to now, our page load size is 152.7 KB. Check out this commit will give you this minimal set.
4. +Material-UI
Material-UI is a popular React component UI set to provide well-designed components like Button, Menu, etc. Let's see how much it costs. Install it:
Then create a special Provider
component for MUI to include MUI-related global setting:
Basically, I added CssBaseline
for a consistent CSS starting base. Also 2 MUI themes to define our dark/light themes. Then I include a very basic MUI Button in the main page src/app/[lang]/page.tsx
to make Next.js includes the library.
Build it, deploy it. Pingdom then gives:
The page size goes from 152.7 KB to 190.5 KB, a 37.8 KB increase. Compared with the full 146.5 KB, this number seems acceptable.
But do take note that this is the minimal. As we use more MUI components, or include other MUI libraries, like mui-lab or mui-icons, more sizes will be added. For example, I add a MUI Dialog, then the bundle size increases to 199.7 KB (this commit). I have no idea, a simple Dialog takes 9 KB!. Probably it asks MUI to load some underlying components. Let's keep an eye on this, whenever we use a new MUI component, such as Dialog, do a page size check, in case it suddenly blows.
OK. Now we have 190.5 KB: Next.js + Material-UI.
5. +FontAwesome
FontAwesome provides thousands of icons with different styles. Follow the official doc here to add libraries @fontawesome/fontawesome-svg-cores, @fontawesome/react-awesome, @fontawesome/pro-solid-svg-icons and @fontawesome/pro-duotone-svg-icons. With the 4, the first 2 are compulsary, while the other 2 needs license to install. For demonstration purpose, you can use the free icons instead.
That's it. Similar to MUI, FontAwesome applies Tree Shaking, which means, only added icons are loaded into the bundle. I wrote a simple wrapper component for the icons:
Then on homepage, add 2 icons for testing purpose:
That's it. Rebuild and redeploy it, then Pingdom says:
So the cost to add 2 FontAwesome icons is 3.6 KB only. I did a little experiment to add 8 more icons, then there are extra 4.9 KB. It's roughly 0.6 KB for each icon. Since I choose to use SVG icons, adding more such icons will make the HTML payload heavier. Check out this commit for the code up to now.
Now we have 194.1 KB for Next.js + Material-UI + FontAwesome.
6. +Redux
Redux is arguably the best front-end data management tool. Despite that the learning curve is steep, it's widely used in a lot of projects. To use redux in Next.js, we need to install 2 packages: react-redux and @reduxjs/toolkit.
There are a bit more to explain here. Please check this commit for how I add Redux to Next.js. I have introduced a HelloWorld Redux implementation, the sizes increases by 15.9 KB.
Looks like it's still acceptable. And the size by redux will not likely to blow as the app evolves.
Now we have 210 KB for Next.js + Material-UI + FontAwesome + Redux.
7. +NextAuth
Now I want to add something to enable authentication/authorization via SSO (GitHub, Google etc.). NextAuth is an ideal candidate. NextAuth.js is a complete open-source authentication solution for Next.js applications.
Checkout this commit. I have added next-auth to support login/logout via GitHub and Google accounts. Well, we do not introduce local database like prisma, the addition of NextAuth adds an extra 3.5 KB, summing up to 213.5 KB.
Now we have a Next.js app with Material-UI + FontAwesome + Redux + NextAuth, the client page size is 213.5 KB, where JavaScript takes up 147.1 KB.
8. Conclusion
The number from the previous section looks alright.
However, as the pages get more features, more JavaScript payload will be added to the client bundle, especially from Material-UI. I tried to add Dialog, which adds 9.x KB. Similarly, Tooltip and Zoom from Material-UI also add around 10 KB. I guess there are some common components to be included among Dialog, Tooltip, etc., such as Paper. But it's still worthy to keep an eye on it. My another website https://myex.ai has similar feature set, but the page load for the blank homepage is around 350 KB. My gut feeling is: Material-UI blows the client-side bundle easily, even with Tree Shaking.