Faster, Leaner and Better Next.js
We always wanted to make Next.js faster in every way possible. That was the main goal of Next.js 2.2 and 2.3. Here are some of the major changes we did across both of those versions:
These changes will give your app a significant page load speed over version 2.0 of Next.js. So, upgrade your Next.js version to the latest right away.
No More JS Inlining
Earlier, we inlined the JS code for a given page with the SSR output. As an example, let’s say you are loading the “/about” page of your app:
As you can see above, we’ve inlined JavaScript code specific to the “/about” page along with the HTML(SSR output). With that, we expected faster page load times. But it was not true. Here are some of the major issues it had:
- The JavaScript code is always static for a given page, even the HTML content changes. So, we miss the opportunity to cache some JavaScript code.
- Sometimes, JavaScript content can be pretty long. So, it will affect the HTML rendering.
Now, we simply include an async script tag (with an src tag) instead of inlining the script. Because we load the JavaScript from a URL, it can be cached and re-used on subsequent requests.
And now the SSR page size is pretty small so the browser can render the HTML content pretty fast.
Async Scripts
Earlier we used render blocking script tags. Now we use a set of async script tags. They were located at the end of the page, so the browser can render the page pretty fast without waiting for the JavaScript content to be loaded or executed.
Script Preloading
With async script tags, we can render our HTML page very quickly without waiting for JavaScript. That’s pretty nice. However, most of our apps usually have dynamic UI elements, like popover menus that require JavaScript.
When the HTML is rendered, JavaScript code is not loaded or initialised yet. So, your users won’t be able to interact with your app for a few seconds until the JavaScript code is initialised.
So a fix for this is a new web standard called link rel=preload. With that, we can ask the browser to load our JavaScript (or any other static assets) in the background.
We do this by adding a link tag in the beginning of our HTML content.
With that, the browser could initialize the JavaScript content pretty quickly.
Currently, this standard is implemented only in Chrome and Opera. (Safari support is on the way.)
Better Code Splitting
We have automatic code splitting support from the beginning. We split code based on Next.js pages. Then common modules will be moved to our main “app.js” file. Previously, a module needed to be used in all of the pages to mark it as a common module.
It works when you have a few pages. But if you have a lot of pages and your app is growing, then that is more challenging.
So, we’ve changed our code splitting logic as below:
If a module is used in at least half of all of the pages, we mark that module as a common module. With that, we could improve the code splitting in Next.js dramatically.
For example, once we apply this change to https://zeit.co, the page size for a given page is 10 times smaller than the previous time. That was pretty amazing.
Using the Minified React
It’s generally recommended to use the minified version of React with your production app even if you are using webpack. With our experiments, there were no big performance improvements when you use this with webpack’s uglifyjs minification.
But if you turned off uglifyjs, you could see some massive improvements.
As an example, let’s consider https://zeit.co.
We’ve turned off uglifyjs because it takes a lot of time and resources to build our app. With this change, we could reduce our “app.js” bundle size from 1100 KB to 600KB. That’s a pretty neat improvement.
Prefetching pages without Service Workers
This is something we shipped with 2.0 but I think it’s better to talk about this here as well. Next.js has a built-in prefetching API where you can decide which pages to prefetch in the background.
We tried to use Service Workers for this purpose and it was pretty hard to implement correctly. Additionally, because Next.js is a framework, the user might need to use his/her own Service Worker script. If Next.js comes with a Server Worker, that could be a problem.
So, we decided to implement a prefetching solution based on plain old HTTP requests and it works pretty well and there are no cons compared with Service Workers. But it allows the app to initialize its own Service Worker script if needed and prefetching works across all of the browsers.
Aggressive Caching
Another possible use case of Service Workers is its caching API. But we implement aggressive caching based on usual HTTP caching APIs and cache our JavaScript assets and HTML very efficiently.
We use usual cache-control headers (including immutable caching) and e-tags to cache our resource aggressively. Here’s a screenshot of the network tab once we reloaded an already loaded page on https://zeit.co.
As you can see, all of the JavaScript assets (including prefetching pages) are loaded from the cache. And we get a 304 header for the HTML page.
As I previously mentioned, Next.js will always send a 304 (Not Modified) header if your page generated via SSR is the same as that version available in the browser cache. (We do it by the help of e-tag headers.)
We Want to Do More
These are some set of minor improvements we’ve done to Next.js over 2.2 and 2.3. Give it a try and let us know what you think about these changes.
We can’t do any of these without the community help as most of these features are pushed by the community via issues or pull requests.
We always need your help and let us know what we can improve. I bet there are a lot of things we could improve together.