Skip to main content

How to code faster, lighter JavaScript

Many small changes can lead to big gains. Enable users to interact with your site with the least amount of friction. Run the smallest amount of JavaScript to deliver real value. This can mean taking incremental steps to get there but, in the end, your users will thank you. Here's some advice for making that happen.

Introduce code splitting

Code splitting (opens in new tab) helps you break up your JavaScript so you only load the code a user needs upfront and lazy-load the rest. This helps avoid shipping a monolithic main.js file to your users containing JavaScript for the whole site versus just what the page needs. 

JavaScript code-splitting

Splitting large, monolithic JavaScript bundles can be done on a page, route or component basis

The best approach to introduce code splitting into your site is using the dynamic import() syntax (opens in new tab). What follows is an example of using JavaScript Modules (opens in new tab) to statically 'import' some math code. Because we're not loading this code dynamically (lazily) when it's needed, it will end up in our default JavaScript bundle. 

import { add } from './math'; 
console.log(add(30, 15));

After switching to dynamic import(), we can lazily pull in the math utilities when they are needed. This could be when the user is about to use a component requiring it, or navigating to a new route that relies on this functionality. Below we import math after a button click.

const btn = document.getElementById('load');
btn.addEventListener('click', () => {
   import('./math').then(math => {
       console.log(math.add(30, 15));
   });
});

When a JavaScript module bundler like Webpack (opens in new tab) sees this import() syntax, it starts code splitting your app. This means dynamic code can get pushed out into a separate file that is only loaded when it is needed.

Code splitting can be done at the page, route or component level. Tools like Create React App, Next.js, Preact-CLI, Gatsby and others support it out of the box. Guides to accomplish this are available for React (opens in new tab), Vue.js (opens in new tab) and Angular (opens in new tab).

If you're using React, I'm happy to recommend React Loadable (opens in new tab), a higher-order component for loading components efficiently. It wraps dynamic imports in a nice API for introducing code splitting into an app at a given component. 

Here is an example statically importing a gallery component in React:

import GalleryComponent from './GalleryComponent';
const MyComponent = () => (
 <GalleryComponent/>
);

With React Loadable, we can dynamically import the gallery component as follows:

import Loadable from 'react-loadable';
const LoadableGalleryComponent = Loadable({
 loader: () => import('./GalleryComponent'),
 loading: () => <div>Loading...</div>,
});
const MyComponent = () => (
 <LoadableGalleryComponent/>
);

Many large teams have seen big wins off the back of code splitting recently. In an effort to rewrite their mobile web experiences to make sure users were able to interact with their sites as soon as possible, both Twitter (opens in new tab) and Tinder (opens in new tab) saw up to a 50 per cent improvement in time to interactive when they adopted aggressive code splitting. 

Audit your workflow

Stacks like Next.js (opens in new tab), Preact CLI (opens in new tab) and PWA Starter Kit (opens in new tab) try to enforce good defaults for quickly loading and getting interactive on average mobile hardware.

Another thing many of these sites have done is adopt auditing as part of their workflow. Thankfully, the JavaScript ecosystem has a number of great tools to help with bundle analysis. Tools like Webpack Bundle Analyzer (opens in new tab), Source Map Explorer (opens in new tab) and Bundle Buddy (opens in new tab) enable you to audit your bundles for opportunities to trim them down.

Lighthouse runs a series of audits against a page and generates a report on it
(opens in new tab)

If you're unsure whether you have any issues with JavaScript performance, check out Lighthouse (opens in new tab). Lighthouse is a tool baked into the Chrome Developer Tools and is also available as a Chrome extension (opens in new tab). It gives you an in-depth analysis that highlights opportunities to improve performance.

We've recently added support for flagging high JavaScript boot-up time to Lighthouse (opens in new tab). This audit highlights scripts that might be spending a long time parsing/compiling, which delays interactivity. You can look at this audit as opportunities to either split up those scripts or just do less work.

Check you're not shipping unused code

Click the icon in the top right to enlarge
(opens in new tab)

Another thing you can do is make sure you're not shipping unused code down to your users: Code Coverage (opens in new tab) is a feature in Chrome DevTools that alerts you to unused JavaScript (and CSS) in your pages. Load up a page in DevTools and the Coverage tab will display how much code was executed vs how much was loaded. You can improve the performance of your pages by only shipping the code that a user needs.

This can be valuable for identifying opportunities to split up scripts and defer the loading of non-critical ones until they're needed. Thankfully, there are ways we can we can try to work around this and one way is having a performance budget in place.

Devise a performance budget

Performance budgets are critical because they keep everybody on the same page. They create a culture of shared enthusiasm for constantly improving the user experience and team accountability. Budgets define measurable constraints so a team can meet their performance goals. As you have to live within the constraints of budgets, performance is a consideration at each step, as opposed to an afterthought. Per Tim Kadlec (opens in new tab), metrics for performance budgets can include:

  • Milestone timings: Timings based on the user experience loading a page (e.g. time-to-interactive). 
  • Quality-based metrics: Based on raw values (e.g. weight of JavaScript, number of HTTP requests), focused on the browser experience.
  • Rule-based metrics: Scores generated by tools such as Lighthouse or WebPageTest; often a single number or series to grade your site.

Performance is more often a cultural challenge than a technical one. Discuss performance during planning sessions. Ask business stakeholders what their performance expectations are. Do they understand how performance can impact the business metrics they care about? Ask engineering teams how they plan to address performance bottlenecks. While the answers here can be unsatisfactory, they get the conversation started.

What about tooling for performance budgets? You can set up Lighthouse scoring budgets in continuous integration with the Lighthouse CI project (opens in new tab). A number of performance monitoring services support setting perf budgets and budget alerts including Calibre (opens in new tab), Treo (opens in new tab) and SpeedCurve (opens in new tab).

4 quick ways to lessen JS load times

Modern sites often combine all of their JavaScript into a single, large bundle. When JavaScript is served this way, download and processing times can be significant on mobile devices and networks. Here are a few tips for how to ensure you load your JavaScript quickly.

01. Only load the JS required for the current page
Prioritise what a user will need and lazy-load the rest with code splitting. This gives you the best chance at loading and getting interactive fast. Learn to audit your JavaScript code to discover opportunities to remove non-critical code.

02. Optimise your JavaScript
Use compression, minification and other JS optimisation techniques. Compression and minification are good optimisations for shipping fewer bytes of JavaScript to your users. If you’re already gzipping JavaScript, consider evaluating Brotli (opens in new tab) for even more savings. Building a site using Webpack and a framework? Tree shaking (removing unused imported code), trimming unused libraries and polyfills, opting for leaner versions of utilities all add up to some nice savings.

03. Assess the UX benefits
If client-side JavaScript isn’t benefiting the user experience, ask yourself if it’s really necessary. Maybe server-side-rendered HTML would actually be faster. Consider limiting the use of client-side frameworks to pages that absolutely require them. Server-rendering and client-rendering are a disaster if done poorly.

04. Embrace performance budgets
Embrace performance budgets and learn to live within them. For mobile, aim for a JS budget of < 170kB minified/compressed. Uncompressed this is still ~0.7MB of code. Budgets are critical to success; however, they can’t magically fix performance in isolation. Team culture, structure and enforcement matter.

Resources

Real-world performance budgets (opens in new tab)
A deep-dive into why performance budgets matter. This guide by Alex Russell questions if we can afford all the JavaScript we load for users on median mobile phones given their impact on user experience. 

Reducing JavaScript payloads with code splitting (opens in new tab)
A practical guide to reducing how much JavaScript you’re loading Webpack or Parcel. It also includes links to code-splitting guides for React, Angular and others.

Reducing JavaScript payloads with tree shaking (opens in new tab)
Tree shaking is a form of dead code elimination. This guide covers how to remove JavaScript imports not being used in your web pages to help trim down your JavaScript bundles.

Lighthouse (opens in new tab)
Lighthouse is a free automated tool for improving the quality of web pages by the Chrome team. It has audits for performance, accessibility and more.

Pinterest case study (opens in new tab)
Pinterest reduced its JavaScript bundles from 2.5MB to < 200kB and reduced time-to-interactive from 23 seconds to 5.6 seconds. Revenue went up 44 per cent, sign-ups are up 753 per cent, weekly active users on mobile web are up 103 per cent (opens in new tab)

AutoTrader case study (opens in new tab)
AutoTrader reduced its JavaScript bundle sizes by 56 per cent and reduced time-to-interactive by ~50 per cent.

This article was originally published in net (opens in new tab), the world's best-selling magazine for web designers and developers. Buy issue 313 (opens in new tab) or subscribe (opens in new tab).

Read more:

Thank you for reading 5 articles this month* Join now for unlimited access

Enjoy your first month for just £1 / $1 / €1

*Read 5 free articles per month without a subscription

Join now for unlimited access

Try first month for just £1 / $1 / €1

Addy is an engineering manager with the Chrome team at Google. His day-to-day job is leading a speed team who are dedicated to making the web fast. Their projects include Lighthouse.