DEV Community

Cover image for Improving website performance by eliminating render-blocking CSS and JavaScript
Adrian Bece for PROTOTYP

Posted on

Improving website performance by eliminating render-blocking CSS and JavaScript

In my previous post, I've talked about how I boosted Lighthouse scores for my personal website by implementing native lazy loading with fallback.

Another important improvement that boosted my performance and Lighthouse score was eliminating render-blocking resources.

Alt Text

Critical and non-critical resources

When we usually build a project, we like to include everything we need right out of the box - all styles, JavaScript plugins, JavaScript code, fonts, images, etc. We usually do this to make sure that no async loading errors happen while we develop the project.

The reality is that browser needs to load, parse and run everything we include when website loads, which can make the first paint (with no cached resources) unnecessarily slow. It's called render-blocking because browser is wasting time and resources parsing the code that is unnecessary for the initial page load and not displaying the page content.

When we take a look at our resources (CSS, JavaScript, fonts, etc.) we can sort them into two categories:

  • Critical resources - code that's critical to the page's core functionality.
  • Non-critical resources - code not being used in page's core functionality and a code that runs after the page is loaded or on user interaction.

So let's take a look how we to handle critical and non-critical CSS and JavaScript resources.

Handling critical CSS

Critical CSS refers to the styles that are necessary for styling above the fold content. Above the fold content that is visible to users when they first load the page (top section of the page).

In order to add critical CSS to the page, we need to remove those styles from the CSS stylesheet and add them directly to HTML in a <style> tag inside a <head> element.



<head>
  <!-- ... -->

  <style>
    /* Add critical styles here */
  </style>

  <!-- ... -->
</head>


Enter fullscreen mode Exit fullscreen mode

Alt Text

Critical CSS on my personal site

This approach may increase HTML document size a bit, but those changes are insignificant if you use compression algorithm like GZIP or Brotli for HTML delivery.

Adding critical CSS directly to HTML document ensures that those styles and parsed and applied on the first paint (initial load).

Handling non-critical CSS

In order to make the critical CSS effective, we need to tell the browser how to handle non-critical CSS and display the page. It also allows us to use the website while the additional non-critical CSS loads. Depending on Internet connection speed, you might not even notice the additional styles being loaded.

In order to handle non-critical CSS, we need to change how the CSS file which contains those styles loads.



<head>
  <!-- ... -->

    <link crossorigin rel="preload" href="/path/to/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
    <noscript><link rel="stylesheet" href="/path/to/styles.css"></noscript>

  <!-- ... -->
</head>


Enter fullscreen mode Exit fullscreen mode

This may look like a hack at first, but this is a really smart and efficient way of loading CSS in an efficient way with proper fallback:

  • link rel="preload" as="style" loads the CSS file in a non-render-blocking way.
  • onload="this.onload=null;this.rel='stylesheet'" makes sure that CSS file is parsed and loaded after the site loads and the onload function is deleted.
  • noscript fallback makes sure that the CSS loads the standard way if JavaScript is not available.

Alt Text

Non-critical styles being loaded on a below-the-fold content. Notice how some (critical) styles are already applied (grey background) before the rest is loaded (non-critical)

It's also important to note that we can load Google Fonts stylesheets in the same efficient way!



<link crossorigin rel="preload" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600&display=swap"></noscript>


Enter fullscreen mode Exit fullscreen mode

Firefox issue & handling IE

At the time of writing this article, Firefox has a bug related to preloading CSS. This efficient way of loading non-critical CSS currently isn't working on Firefox, but it should be fixed soon.

You might want to provide a fallback for browsers that do not support preloading or have issue with it (like Firefox). Luckily, this is really easy to do with inline JavaScript.



<script>
    var isIE = !!window.MSInputMethodContext && !!document.documentMode;
    var isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;

    if (isIE || isFirefox) {
        var pageStylesheet = document.createElement("link");
        pageStylesheet.rel = "stylesheet";
        pageStylesheet.type = "text/css";
        pageStylesheet.href = "/path/to/styles.css";
        document.head.appendChild(pageStylesheet);
    }
</script>


Enter fullscreen mode Exit fullscreen mode

We just need to add this code before body closing tag to insert the regular link element into head element for Firefox and IE browsers which do not support preloading.

Handling critical JavaScript

We handle critical JavaScript the similar way we handle critical CSS, by inlining it within HTML code. It's important to note that we need to insert critical JavaScript code using script tag before the body closing tag. That way we make sure that JavaScript doesn't block content render and all available DOM nodes are created and available to JavaScript code.



<body>
<!-- ... -->

<script>
/* Inlined JavaScript code */
</script>
</body>

Enter fullscreen mode Exit fullscreen mode




Handling non-critical JavaScript

We can handle non-critical JavaScript just by adding defer or async tags to script tag (inline JavaScript or JavaScript loaded from src).

  • We use defer for scripts that need the whole DOM and/or their relative execution order is important. It tells the browser to load the page first and then load the script in the background.
  • We use async for independent scripts that can be executed in any order. This script doesn't wait for any other scripts and can loaded in parallel with other scripts with async scripts.


<script defer src="/path/to/script.js"></script>
<script async src="/path/to/script.js"></script>

Enter fullscreen mode Exit fullscreen mode




Boosted performance & Lighthouse score

On my personal website I've handled critical and non-critical CSS and JavaScript as I've described in the article. After implementing this modern approach, I've eliminated render-blocking, non-critical CSS and JavaScript which in turn boosted my Lighthouse score and overall performance!

Alt Text


These articles are fueled by coffee. So if you enjoy my work and found it useful, consider buying me a coffee! I would really appreciate it.

Buy Me A Coffee

Thank you for taking the time to read this post. If you've found this useful, please give it a ❤️ or 🦄, share and comment.

Top comments (18)

Collapse
 
zachdixon profile image
Zachary Dixon

@adrianbdesigns great article and just in time, we're currently working on this at my workplace :) Thanks!

Collapse
 
adrianbdesigns profile image
Adrian Bece

Thank you! Glad you found the article useful. Cheers!

Collapse
 
louislow profile image
Louis Low

Artis is a low-level and functional virtual CSS library with no CSS codes. More than 80 Utilities. Infinite Configurations. (artisjs.netlify.app) #virtualcss #virtualdom #javascript #cssinjs #csslibrary

Image description

Collapse
 
dailydevtips1 profile image
Chris Bongers

Nice article! Enjoyed it! 🔥

Collapse
 
leotocca profile image
Leonardo Toccaceli

Amazing article! We were just working on performance improvement at work. Thanks a lot!

Collapse
 
rajeshroyal profile image
Rajesh Royal

you can't be sure about critical CSS also when you do updates you have to regenerate it.

Collapse
 
thomasledoux1 profile image
Thomas Ledoux

At my company we use github.com/addyosmani/critical in Gulp to generate the critical CSS at build time, so we always have an up to date version.

Collapse
 
rajeshroyal profile image
Rajesh Royal

that's great 👍

Collapse
 
billionaire9 profile image
Anuwong Mongkudkornburee

Very simple but powerful! Thanks :D

Collapse
 
adriangrigore profile image
Adrian Emil Grigore

Great work! Thanks!

Collapse
 
ender_minyard profile image
ender minyard

It’s very simple to get a 100 for accessibility on Lighthouse. You should work on that next.

Collapse
 
trifit profile image
David

I wouldn't define it as simple, in some cases it might but often truly accessible and inclusive websites require a lot of thought and planing.

That being said every website should try to be as accessible as posible, otherwise you are excluding a big part of your audience, which is bad for your audience and for yourself.

Also, if we only have as source of truth Google lighthouse we might be missing important points (now or in the future), an automated tool has its limits.

Collapse
 
ender_minyard profile image
ender minyard • Edited

"Truly accessible and inclusive websites require a lot of thought and planning." Absolutely. Lighthouse does not even audit for what is required of a truly accessible website, which is why I'm surprised the author couldn't reach such a low bar.

Lighthouse does have its limits. If I really want to audit basic accessibility checkpoints of my website I use wave.webaim.org/ because Lighthouse sets too low of a bar. Every website should try to be accessible as possible and Lighthouse doesn't even test for enough.

Collapse
 
mak0099 profile image
Maulik Thaker

Nice one. But how can we get rid from unused CSS and JavaScript in Angular build ?

Collapse
 
yasionfire profile image
Yasio

For CSS maybe give purgecss.com/ a try.

Collapse
 
costinmanda profile image
Costin Manda

You very nicely explained what defer and async do, but what about defer and async at the same time on the same script tag? I've seen this in some code bases.