How to get 100 website performance score
First, let’s see what 100 website performance score means. A tool, called Lighthouse, can be 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 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 can also use web.dev or PageSpeed Insights
Here is the performance score of this website using web.dev:
The performance score is determined by 3 metrics:
- 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.
- First Input Delay (FID): measures interactivity. To provide good user experience, pages should have an FID of less than 100 milliseconds.
- 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. This is why I did it anyway.
Why you should not go for it? If your website is very interactive, uses buttons to fetch data, 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 loads 200k of JavaScript, including React. When React loads it adds interactivity to your buttons, fetches data, etc.
Because this website has only links, no buttons, no data fetch, I removed all the JavaScript added by Gatsby using
the 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 sitename 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 plain
JavaScript 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 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/&text=How%20to%20Get%20100%20Website%20Performance&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 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. This way 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. This way 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.