How to score 100 on PageSpeed Insights while using NuxtJS SSR

How to score 100 on PageSpeed Insights while using NuxtJS SSR

Created
Jun 27, 2022
Tags
Tech
The “Page Experience ranking update” from Google has been completely rolled out, and Core Vitals are now ranking factors for Google.
At RefurbMe, we are using NuxtJS along with vue2, and vuetify for all the UI components. Without any optimization, we end up with a low score that is usually hard to beat.

Lazy load all JS scripts

The great thing about server-side rendering your pages is that you can have your HTML ready to use without the need for any javascript.
So we are going to load all javascript only after the first interaction (mouseover, keydown, touchstart, touchmove, wheel), or after 2500ms of inactivity.
on your NuxtJS project, create app.html on the root of your project with the following code:
<!DOCTYPE html> <html {{ HTML_ATTRS }}> <head {{ HEAD_ATTRS }}> {{ HEAD.replace(new RegExp('<link rel="preload" href="/_nuxt/([a-f0-9]{7})\.js" as="script">', 'g'), '') }} </head> <body {{ BODY_ATTRS }}> {{ APP.replace(new RegExp('script src="/_nuxt/([a-f0-9]{7})\.js"', 'g'), 'script lazy-src="/_nuxt/$1.js"') }} <script type="text/javascript"> const lazyLoadTimeout = 2500; // ms const userInteractionEvents = ["mouseover", "keydown", "touchstart", "touchmove", "wheel"]; const loadScriptsTimer = setTimeout(loadScripts, lazyLoadTimeout); function loadScripts() { Array.from(document.querySelectorAll("script[lazy-src='lazy']")).forEach(function(elem) { elem.setAttribute("src", elem.getAttribute("lazy-src")); elem.removeAttribute("lazy-src"); }) } userInteractionEvents.forEach(function(event) { window.addEventListener(event, triggerScriptLoader, { passive: true}) }); function triggerScriptLoader() { loadScripts(); clearTimeout(loadScriptsTimer); userInteractionEvents.forEach(function(event) { window.removeEventListener(event, triggerScriptLoader, { passive: true }) }) } </script> </body> </html>
/app.html
On the head section, we prevent preloading all the _nuxt bundle files. And on the body section, we replace all nuxt scripts attributes from src to lazy-src.
And when we are ready to load the scripts, we revert to set back the src attribute to load them.

Lazy load images

Another optimization would be to avoid loading any image that is not located above the fold. And it would be only loaded when we scroll enough to see it.
We use the library lazysizes for this as a NuxtJS plugin (don’t forget to add it to your nuxtjs.config.js):
import lazySizes from 'lazysizes' export default lazySizes
/plugins/lazysizes.js
And for every image, you can add a class name lazyload:
<img src="/myimage.png" class="lazyload" />
For more examples, you can check their documentation.

Use caching extensively on CDN

We use Cloudflare to manage our DNS and Caching. We just added Page rules to enforce that all static files are cached to speed up loading time:
  1. URL: <domain>/assets/* Settings: - Browser Cache TTL: a year - Cache Level: Cache Everything - Edge Cache TTL: a day
  1. URL: <domain>/_nuxt/* Settings: - Browser Cache TTL: a year - Cache Level: Cache Everything - Edge Cache TTL: a day
  1. URL: <domain>/* Settings: - Cache Level: Cache Everything - Edge Cache TTL: an hour (you can adjust this based on your site and how frequent the pages change, and you’d need to purge the cache if you want avoid one hour to see any new changes on the site)

Optimize imports

We use the module @nuxtjs/vuetify for all UI components, and it can be pretty heavy especially if you are not using all components.
One option would be to set to true the option treeShake on the nuxt.config.js:
vuetify: { treeShake: true //... }
/nuxtjs.config.js
Also if you are using icons, instead of loading all the icons as a font, we hand pick the ones that we to use as SVG paths.
On the nuxtjs.config.js, we replace the defaultAssets as the following:
vuetify: { defaultAssets: { font: false, icons: 'mdiSvg' // change this based on your usage }, //... }
/nuxtjs.config.js
and when you import icons, you can do it like the below example:
<template> <v-icon>{{ mdiAccount }}</v-icon> </template> <script> import { mdiAccount } from '@mdi/js' export default { data () { return { mdiAccount } } } </script>
/components/my-example.vue

Remove unused CSS

While using UI components libraries like Vuetify, we end up with a lot of CSS that is not used (Especially if you are not supporting dark mode, right-to-left layouts, or any helper class names).
We use the library postcss-purgecss and css-byebye to remove CSS declaration that we think we do not need.
build: { ...(process.env.NODE_ENV !== 'production' ? {} : { // ADJUST THIS BASED ON YOUR USAGE AND NEED, THIS IS JUST AN EXAMPLE postcss: { plugins: { '@fullhuman/postcss-purgecss': { content: [ 'components/**/*.vue', 'layouts/**/*.vue', 'pages/**/*.vue', 'plugins/**/*.js', 'node_modules/vuetify/src/**/*.ts' ], styleExtensions: ['.css'], keyframes: true, safelist: { standard: [ 'body', 'html', 'nuxt-progress', /col-/, /-(leave|enter|appear)(|-(to|from|active))$/, /^(?!(|.*?:)cursor-move).+-move$/, /^router-link(|-exact)-active$/, /data-v-.*/, /v-data-table.*/, /v-data-table--mobile/, /lazyload/ ], deep: [ /page-enter/, /page-leave/, /dialog-transition/, /tab-transition/, /tab-reversetransition/, /v-progress-circular/, /-(indeterminate|loading)$/, /--mobile$/, /v-input__prepend-inner/ ] } }, 'css-byebye': { rulesToRemove: [ /.*\.v-application--is-rtl.*/ ] } } } } ), // ... }
/nuxtjs.config.js

Conclusion

These are a few recommendations that would do 80% of the optimizations, every site would use different libraries and components and would need specific optimizations.
These are the optimizations that we used that allowed us to score 100 on the page speed insights for RefurbMe:
 
I’m sure there are different ways to optimize for speed, but lazy loading the JS files had the biggest impact in our case.
That was also the case for visalist.io:
To join the discussion about this article, you can contribute on this twitter thread.

This article is brought you by tonoïd – we are a micro-startup studio building small businesses that are profitable and solve a specific problem without any external funding nor billion-dollar market-size. Most notably, RefurbMe, a comparison site for refurbished products – and Notion Automations.
If you have any feedback, do not hesitate to reach out at
✉️
Contact