How to Get 100 Website Performance Score
Tags: javascript, gatsby, webperf
Published: 2020-07-07 06:33:45


First, let’s see what 100 website performance score means. A tool, called [Lighthouse](https://developers.google.com/web/tools/lighthouse), is used to measure website performance. The measurement is a score between 0 and 100. 100 means the best performance.

This score is important in several ways:

- It’s an indicator of the user experience. If the website is slow, visitors will not wait and move on to other sites. This is even more important on mobile devices, which in general have a slow internet connection.
- It’s part of the Google Search Engine algorithm that determines the rank of the website for a search query. The lower the score, the lower the website ranking, given there are other sites that match the search query and have a better score.

Lighthouse is integrated into Chrome’s Developer Tools so you can use it from there, but you should use [web.dev](https://web.dev/measure/) or [PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/) because the DevTool's score is influenced by Chrome plugins you have installed.

The tips presented here can be applied to any website, but as an example I'll use my website, [https://www.raresportan.com](https://www.raresportan.com).

Here is the performance score of my website using web.dev:[![The performance score of this website](https://raresportan.com/static/772422e4c6077575d4fc47afd461bf7e/c5bb3/perf.png "The performance score of this website")](/static/772422e4c6077575d4fc47afd461bf7e/eee07/perf.png)

The performance score is determined by [3 metrics](https://web.dev/vitals/):

1. Largest Contentful Paint (LCP): measures loading performance. To provide good user experience, LCP should occur within 2.5 seconds of when the page first starts loading.
2. First Input Delay (FID): measures interactivity. To provide good user experience, pages should have an FID of less than 100 milliseconds.
3. Cumulative Layout Shift (CLS): measures visual stability. To provide good user experience, pages should maintain a CLS of less than 0.1.

## Why Go for 100?

A score of 90+ is great, you are doing a great job. So why would someone want to go for 100?

There is no good answer to this question. There’s not much of a difference between 90+ and 100 scores. People will have the same experience. The rank on Google will be the same. You’ll go for 100 only if you want to demonstrate that you can. That you go the extra mile. This is why I did it anyway.

Why you should not go for it? If your website is very interactive, fetches data, uses buttons to post data, so is very dynamic, you’ll have a hard time reaching 100 because you’ll need to load too much JavaScript.

## How to Do It

This can be done by removing a lot of JavaScript, embedding CSS and fonts and using less images and optimizing the images.

### 1. Remove Javascript

JavaScript affects LCP & FID.

I’m using Gatsby to build the website, the pages are rendered server-side and served as HTML to the browser. But after the HTML is loaded, it also loads 200k of JavaScript, including React. When React loads it adds interactivity to your buttons, fetches data, etc.

Because the website has mostly links, few buttons, no data fetch, I removed all the JavaScript added by Gatsby using the [`gatsby-plugin-no-javascript`](https://www.gatsbyjs.org/packages/gatsby-plugin-no-javascript/) in `gatsby-config.js`

```
module.exports = {
  plugins: [
      ...
      `gatsby-plugin-no-javascript` ]
}      
```

I have a button the website, the lightbulb next to the site name is used to change the theme. That was not working anymore after I’ve removed all the JavaScript, but I’ve solved this by reimplementing the functionality in plainJavaScript inside `html.js`:

```
 <script
    dangerouslySetInnerHTML={{
    __html: `
        var theme;
        try {
            theme = localStorage.getItem('theme');
        } catch (err) { }

        if(!theme && window.matchMedia('(prefers-color-scheme: dark)') && window.matchMedia('(prefers-color-scheme: dark)').matches) {                
            theme = 'dark'
        }               
        document.body.className = theme || 'light';

        function toggleTheme() {
            var body = document.querySelector('body');
            var newTheme = body.className === 'dark' ? 'light' : 'dark';                
            body.className = newTheme;
            try {
                localStorage.setItem('theme', newTheme);
            } catch (err) { }
        }`
    }}
/> 
```

Another thing that I did was to call the [analytics function](/server-side-google-analytics/) from `html.js` also, this way I don’t include Google Analytics scripts on the website:

```
<script
    dangerouslySetInnerHTML={{
    __html: `
        // send analytics data
        function sendData() {
            var sitedata = {
                ...
            }
            return fetch('/.netlify/functions/send', {
                body: JSON.stringify(sitedata),
                method: 'POST'
            })
        }
        sendData();
    `
    }}
 />      
```

If you are using Twitter share on your website you might need to remove the Twitter library and replace the calls with plain links. Here is an example:

```
<a href="https://twitter.com/share?url=https://raresportan.com/how-to-get-100-performance/&amp;text=How%20to%20Get%20100%20Website%20Performance&amp;via=raresportan" 
   target="_blank" 
   rel="noreferrer">
    Please share it with your friends on Twitter    
</a>
```

### 2. Embed Critical Styles

CSS files affect CLS. If the CSS is loaded after the HTML is rendered, the page visuals are changing.

Critical CSS must be added inside the HTML using a `<style>` tag. Do not use a `.css` file for your critical CSS.Lucky me, Gatsby does this by default. It concatenates the content of all CSS files in a single string that is added as `<style>` tag inside the HTML.

### 3. Embed Fonts

Just as CSS, fonts affect CLS. The moment the font is loaded, the texts on the page are re-rendered. And just as CSS, the fonts must be in the HTML, and not loaded as separate files.

In my case, I creates a fonts.css that contains the font sources as base64 encoded strings. They end up inside the`<style>` tag with the rest of the CSS.

```
@font-face {
    font-family: IBM Plex Sans;
    font-style: normal;
    font-display: swap;
    font-weight: 500;
    src: url(data:font/woff2;base64,d09GMgABAAAAAEjQABEAAAAAy...)
```

I used a `base64` command (available on macOS and Linux) to transform the fonts:

```
$ base64 myfont.ttf > fontbase64.txt
```

Alternatively, you can use an online service to do this.

### 4. Optimize Images

You should use few images if possible. If not make sure you optimize the hell out of them.Always set a width and height or put them inside a container that has ‘overflow: hidden’, otherwise when an image is loaded it moved content around and this is very bad for the CLS.

I’m using Gatsby’s plugins to optimize my images. It automatically generates different images for different viewport sizes and loads the images lazy:

```
<img class="gatsby-resp-image-image" 
    alt="The performance score of this website" 
    title="The performance score of this website" 
    src="/static/772422e4c6077575d4fc47afd461bf7e/c5bb3/perf.png" 
    srcset="/static/772422e4c6077575d4fc47afd461bf7e/04472/perf.png 170w,
            /static/772422e4c6077575d4fc47afd461bf7e/9f933/perf.png 340w,
            /static/772422e4c6077575d4fc47afd461bf7e/c5bb3/perf.png 680w,
            /static/772422e4c6077575d4fc47afd461bf7e/b12f7/perf.png 1020w,
            /static/772422e4c6077575d4fc47afd461bf7e/b5a09/perf.png 1360w,
            /static/772422e4c6077575d4fc47afd461bf7e/eee07/perf.png 1628w" 
            sizes="(max-width: 680px) 100vw, 680px" 
            loading="lazy" 
            style="width: 100%; height: 100%; margin: 0px; vertical-align: middle; position: absolute; top: 0px; left: 0px;">

```

Beside this I’m using a [Netlify plugin](https://www.npmjs.com/package/netlify-plugin-image-optim) that optimizes the image even further.

### 5. Preload Pages

One of the nice things Gatsby does is that it preloads all the pages referenced by a specific page. And thus navigation from one website page to another is instant.

I loved that. But now that I’ve removed JavaScript, the navigation between pages is much slower.I almost give up at this point. While the initial page load was faster, the in-site navigation was worse.

In the end, I wrote a bit of plain JavaScript that prefetch a page when the user hovers on the link. With this I save 100-300ms and the page are appearing to load faster:

```
<script
    dangerouslySetInnerHTML={{
    __html: `
        window.addEventListener('DOMContentLoaded', (event) => {
            document.querySelector('button.lightbulb').addEventListener('click', toggleTheme);

            //only in-site links
            var links = document.querySelectorAll('a[href^="/"')
            links.forEach(function(link) {
                link.addEventListener('mouseover', function(e) {
                var l = e.target;
                var href = l.href; 
                var link = document.querySelector('link[href="'+href+'"]');
                if (!link) {
                    var prefetchLink = document.createElement("link");
                    prefetchLink.href = href;
                    prefetchLink.rel = "prefetch";
                    document.head.appendChild(prefetchLink);
                }
            })
        });        
    `,
    }}
/>
```

## Conclusion

If you are willing to make some compromises, most importantly to replace kilos of JavaScript libraries with some vanilla JavaScript you can reach a 100 score in website performance.

While you can do something about CSS and fonts, in most cases is probably not practical to remove all the JavaScript, and a 90+ score is fine.

---
Thanks for reading. This article was originally posted on [my blog](https://www.raresportan.com/how-to-get-100-performance-score/).
Cover Photo by ShareGrid on Unsplash