Linkedin Tag

Back to blog

How to speed up JavaScript

Monday, September 2nd, 2024

Updated November 28th, 2024

M

Marketing

Eliminate render-blocking resources, reduce unused JavaScript and minimize main thread work are usually found right on top of the PageSpeed Insights report. They talk about potential savings, but besides using the defer tag, there isn’t much info on how to do this.

Though there are a few extra ways to get your pages loading faster by tackling JavaScript. 

Let’s get deferring out of the way first, and then give you some extra options.

Defer or async?

In short - deferring loading scripts makes your site appear to be loading faster than using ‘async’. Depending on your usage, it might or might not be the best way to go however.

The ‘defer’ attribute allows the browser to continue parsing HTML while the script is downloaded in the background. But it waits on the execution of the script until after the HTML parsing is complete. This is why the content ‘paint’ on your site is loading faster. The webpage is fully loaded for you visitor to see quicker, than with no or the ‘async’ attribute.

The ‘async’ attribute allows the browser to continue parsing the HTML content while the script is being downloaded in the background. But once the script is downloaded, it executes immediately. Should the HTML parsing not be ready, it would interrupt this. So while the HTML loads faster than without using async, the potential interruption of loading the HTML might make your site appear to load slower.

When do you use defer or async?

‘Async’ scripts execute as soon as they are downloaded, which is not necessarily in the order in which they appear in the document. The ‘defer’ attribute preserves the order and ensures that scripts execute after the document has been parsed.

‘Async’ is particularly useful for scripts that do NOT rely on any Document Object Model (DOM) elements or other scripts. A deferred script waits until the DOM is ready, making them a safer bet for scripts that need to manipulate the DOM.

What type of scripts manipulate the DOM?

Libraries like jQuery or custom scripts in general. These manipulate the DOM and should be loaded with the ‘defer’ attribute to ensure the DOM is fully loaded before they run. And, if you have scripts that rely on other scripts to be loaded first, using ‘defer’ ensures they are executed in the order they appear in the document.

Analytics, ads and less vital things scripts, generally speaking, don’t need to be loaded as fast as possible. It’s a choice you have to make yourself, but most people prefer a faster loading site compared to a few milliseconds faster working scripts.

A good rule of thumb is to use defer for non-essential JavaScript. If the script does not depend on the DOM or on other scripts, then using async or no attribute is fine.

Reduced unused JavaScript

Time to do some spring cleaning. Shake the tree and get rid of dead code, here meaning unused JavaScript. This doesn’t only benefit speed, but also security. We have written multiple articles thus far on the issues that can come with third-party JavaScript. Our whole company was founded in order to help secure against the vulnerabilities associated with it. And oftentimes, websites continue to run scripts that are no longer used with great risks that come with it.

This was recently the case in the Polyfill web supply chain attack, where an old domain was bought by a new party, and then used for malicious reasons. Over 490,000 websites had referenced this domain and potentially suffered attacks.

Other ways are in the form of tools that were once used but no more, or frameworks that come with a plethora of JavaScript libraries that aren’t in use on a particular site. Read more on the risks of how expired domains can lead to cybersecurity issues here.

The more JavaScript you have on your site, the larger a risk you run.

It used to be practically impossible to spot changes to third-party scripts. Luckily this is no longer the case. We’re very proud to have built a solution that not only monitors and alerts, but is even able to block malicious scripts autonomously before attacks come through.

If you use a monitoring tool like c/side, you’ll easily be able to see what code is being loaded where, as well as what that code actually does. With this information, it’s much easier to perform this spring cleaning and remove unwanted scripts.

But c/side even goes one step further, which brings us to the next point.

Optimizing JavaScript to load faster

c/side loads all the other scripts on your site in a proxy to analyze it before it gets loaded in the browser. This is by far the safest solution. Nothing malicious can touch the browser of your users, which secures them and yourself fully.

But this comes with a few challenges. Since the JavaScript is checked before it loads, this naturally adds latency. Simply deferring isn’t always possible since we load all the scripts in the proxy, also the necessary ones which impact the Document Object Model (DOM) elements as explained before.

For some applications, this would still be fine. For others, this would be a deal breaker.

For us, this was also a dealbreaker. Yes, security first. But if the downsides get too big, companies will hesitate to adopt the best standards. And we must make sure there are as little downsides as possible.

Which is why we’ve engineered c/side to actually optimize scripts to load faster than normal, mitigating the latency completely. Oftentimes even making these scripts, and thus websites, faster instead of slower.

Since we check every session all of the time, we come across the same scripts thousands of times a day. We store every version of a script, and if possible, cache it. This makes it so we can load the cached versions, which is faster than even loading the original versions from new.

We specially rewrite caching headers so that we block a script and still have it be blocked for customers who might already have it in their cache, while maintaining caching performance.

Additionally, we’ve engineered our infrastructure to work incredibly fast. Compared to other vendors who shall remain unnamed, our proxy serves a 20.3 kb script faster than they can serve a 1.2 kb script. Ours took 10ms while theirs took 13ms in our tests, a speed gain of 22x, assuming all other factors remain constant.

Finally, JavaScript is often minified and obfuscated. Minifying is a common way to get some small speed gains. The latter can have negative effects on performance. Just know that in c/side, you can see the deobfuscated versions of the scripts to better understand what the code is doing.

Compression

Another way to optimize JavaScript, is to use GZip, Brotli, or another compression. They are algorithms that reduce the size of files sent from the server to the browser. It works by identifying and eliminating redundant data within a file.

There are a few caveats to this, but it's usually better to use it rather than not. It does take time to zip and unzip the files, but typically less than the time saved by downloading a larger file. Therefore, you still come out on top.

This works specifically well on text files, like HTML, CSS and also JavaScript.

Preloading and prefetching

Preloading allows you to fetch critical resources (like JavaScript) before they are needed by the browser. These resources are then available as soon as they are required, reducing load times. The browser will prioritize these resources, downloading them during the initial page load.

For example, preloading a JavaScript file ensures it’s available when the browser reaches the point in the HTML where it would normally load that script. This prevents delays caused by the browser needing to fetch the file at that point.

This might sound the same as the ‘defer’ or ‘async’ attributes, but there are some key differences.

Preloading ensures critical resources like CSS, fonts, and important JavaScript files are fetched early and prioritized by the browser for immediate availability.

Remember that the ‘defer’ attribute is used to delay the execution of JavaScript until the HTML document has been fully parsed. And then again, the ‘async’ attribute allows scripts to be fetched and executed as soon as they are available, without waiting for HTML parsing to complete.

Another example of preloading is using a custom font on your site. If this font is not loaded quickly, users might see a default font first, leading to a flash of unstyled text. By preloading the font, you ensure that this does not occur.

Prefetching then, focuses on fetching resources during the browser's idle time for future needs, such as resources for the next page the user is likely to navigate to. Loading during those idle times reduces waiting times significantly.

Minimize main-thread work

The main-thread is where the browser does most of the work needed to display a page, such as parsing and executing HTML, CSS, and JavaScript. Keeping it fast and efficient is needed for a generally good user experience.

PageSpeed Insights gives some tips on this.

Rendering can be optimized by sticking to compositor-only properties. These are CSS properties that can be handled entirely by the browser's compositor, bypassing the main thread and the need for layout or paint operations. Simplifying paint complexity by reducing the areas that need to be painted can further help the browser render the page more efficiently, speeding up load times.

When it comes to style and layout, simplifying your CSS and avoiding complex selectors can significantly reduce the time spent on style calculations. This can be achieved by reducing the scope and complexity of style calculations. It’s likely going to be a balance between design and performance.

Avoiding layout thrashing is essential. Layout thrashing occurs when we perform consecutive reads and writes to the DOM, preventing the browser from optimizing the layout. This forces the browser to calculate a layout that is never rendered to the screen.

Consider deferring non-critical CSS, loading non-essential styles asynchronously to prevent them from blocking main thread operations during the critical rendering path.

Back to JavaScript.

For script evaluation, debouncing input handlers is useful. It helps reduce the frequency of event handlers being called, thereby lessening the load on the main thread. Note that web workers have a restricted environment. They cannot directly manipulate the DOM or use certain APIs available to the main thread.

Finally, garbage collection can be optimized by monitoring memory usage. Using tools like ‘measureMemory()’ helps track and manage memory usage, reducing the time spent on garbage collection. Efficient memory management helps keep the main thread free for more critical tasks.

Linking performance to safety

All these solutions will help you to speed up JavaScript to achieve better performance. c/side can help by caching and optimizing scripts too.

But we’d like to stress that first and foremost, we’re all about safety. The fact that we speed up scripts was mostly needed for adoption by our users. Browser supply chain attacks are on the rise. And bad actors often target third-party JavaScript. c/side analyzes each script before they load in the browser and blocks anything malicious.

A proactive approach that saves both you and your users before anything bad happens.

You can get started with c/side for free.