Maximizing Performance in a Vue/Nuxt Project with Sirv

My latest project is Sirv Experts, a directory of professionals proficient with Sirv that we can vouch for. Sirv Experts

The project is powered by Nuxt and has some tricky parts about it, like an interactive map of experts closest to you, a portfolio showcase of each expert, and lots of images all over the website.

It’s a perfect project to test Sirv’s performance and see how it can be optimized to the max.

The problem

We’ve compiled a list of things that we need to optimize for the project, and it’s quite a list:

  • Optimizing images, serving them in the optimal format and size on the fly
  • Lazy loading images
  • Improving first contentful paint & reducing layout shift
  • Hosting static assets on a CDN
  • Showcasing experts’ portfolios consisting of various media like images, videos, 360 spins and 3D models

The solution

I’ll start with some boasting, of course. ๐Ÿ˜€

haha, so good GTmetrix score of 100%, and a 0.5s largest contentful paint. so sick

And a 99% score on LightHouse. Yeah, we’re working on accessibility right now, I know it’s important. ๐Ÿ˜€

Image optimization and lazy loading

The first two problems might look pretty time-consuming, but Sirv has a solution for them out of the box. Automatic responsive images basically covers all that we needed. The images are lazy loaded, served in the optimal format and size, on-the-fly.

Sirv achieves this by grabbing the master image and converting + resizing it based on the users’ device, which requires the Sirv.js script. We’ve tried to just load it in the head section of nuxt.config.js, but later figured out it’s more efficient to load it only on pages that need it by using a simple method call in the mounted hook.

//components/footer.vue
getSirv()
    {
    return new Promise((resolve) => {
        const script = document.createElement('script')
        script.src = 'https://scripts.sirv.com/sirvjs/v3/sirv.js'
        script.type = 'text/javascript'
        script.setAttribute('async', '')
        script.setAttribute('defer', '')
        script.onload = resolve
        script.onerror = () => {
            reject(new Error('Failed to load the Sirv script'));
        };
        document.body.appendChild(script)
    })
}

Alternatively, just use the npm module.

Since our backend already had all images hosted at Sirv (there’s an integration for most platforms), it just worked ๐Ÿ˜€. Yet one more issue remained โ€” while the images are being fetched, we need some sort of placeholder. Which leads us to the next problem to solve.

Improving first contentful paint & reducing layout shift

This is where Sirv’s Dynamic Imaging features came in handy, by simply adding a parameter to the image URL, we can get a placeholder image of any size, format, and color. So we dropped the quality to 10% for the placeholder image, which worked perfectly for small images on the map page

<img
  class="Sirv"
  :src="icon+'?q=10'"
  :data-src="icon"
  :alt="title"
>

Adding blur works pretty nice for bigger images, but we didn’t really have any use-case for this.

We’ve also utilized preload for critical (above the fold) images and prefetched the Sirv CDN and Google Fonts domains. Quite easily done via nuxt.config.js:

//nuxt.config.js -
head: {
    //your meta and other stuff
    link: [
        { rel: 'preconnect', href: 'https://scripts.sirv.com/', crossorigin:true},
        { rel: 'preconnect', href: 'https://experts-content.sirv.com', crossorigin:true},
        { rel: 'preconnect', href: 'https://fonts.googleapis.com', crossorigin:true},
        { rel: 'dns-prefetch', href: 'https://scripts.sirv.com'},
        { rel: 'dns-prefetch', href: 'https://experts-content.sirv.com'},
        { rel: 'preload', as: 'style', href: 'https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,300,400,600&display=swap' }
    ]
}

Hosting static assets

We just slapped all of our static assets on the CDN ๐Ÿ˜€ It’s pretty much a single line in the nuxt.config.js file:

//nuxt.config.js
build: {
    publicPath: 'https://experts-content.sirv.com/_nuxt/'
//your other options
}

You’d still have to upload your assets to the CDN after every build, which can be done via a github action (docs here) or a deployment script like this.

So now we have all of our images optimized, lazy-loaded, and hosted on the CDN, but we still have to deal with the experts’ portfolios.

Showcasing experts’ portfolios

Easy-peasy-lemon-squeezy, we just need to use Sirv’s Media Viewer to create a custom gallery for each expert. All achieved via a simple custom component that grabs experts’ portfolio items and builds the SMV gallery based on the data.

The results are pretty cool, check it out here. Or here’s a random nintendo switch gallery if you’re lazy:

The code for this gallery is simple and self-explanatory:
<div class="Sirv">
 <div data-src="https://demo.sirv.com/demo/Switch/switch-front.jpg" data-type="zoom"></div>
 <div data-src="https://demo.sirv.com/demo/Switch/switch-separate.png" data-type="zoom"></div>
 <div data-src="https://demo.sirv.com/demo/Switch/nintendo_switch.glb"></div>
 <div data-src="https://demo.sirv.com/demo/Switch/switch-slide.jpg" data-type="zoom"></div>
 <div data-src="https://demo.sirv.com/demo/Switch/switch.mp4" data-options="autoplay:true"></div>
 <div data-src="https://demo.sirv.com/demo/Switch/switch-wide.jpg" data-type="zoom"></div>
 <div data-src="https://demo.sirv.com/demo/Switch/switch-oled.jpg" data-type="zoom"></div>
</div>

Clean and simple. We have a really nice lib for Vue3 to make working with SMV a breeze.

UPD: I’ve recently written an integration with Nuxt Image, which makes working with Sirv images in Nuxt freaking amazing.