From 72 to 98: How 22 Commits Fixed a Nuxt Storefront's Lighthouse Performance

From 72 to 98: How 22 Commits Fixed a Nuxt Storefront's Lighthouse Performance

Published by BaseStation Private Limited For software architecture, system design, or backend services, visit base-station.io. More technical articles at blog.base-station.io.


A production Nuxt storefront was functional. Pages loaded. Nothing was broken. But the Lighthouse Performance score on mobile sat at 72, and desktop at 82. Neither is catastrophic, but both represent real user cost — slower paint, delayed interactivity, and a hero image that arrived later than it should.

Twenty-two commits later, desktop hit 98. Mobile reached 89. Accessibility, Best Practices, and SEO held steady throughout. No framework migration. No infrastructure overhaul. Just methodical removal and deferral of what was slowing things down.

Here is what actually happened.


Start With an Inventory, Not Code

The first commit was a checklist: every homepage resource, third-party script, font request, and preconnect, mapped against what Lighthouse actually measures.

This step is easy to skip. It also makes everything after it faster. Within an hour, three cost centres were visible: Google Fonts blocking render, a Shopify web components script sitting in the <head>, and hero images with no size hints competing for bandwidth on first paint.

A performance budget script (perf-budget.mjs) was added to enforce JS and CSS size limits in CI. Regressions now surface before the next audit, not after.


LCP: The Hero Image Was the Problem and the Fix

The hero slider had three slides. All three were treated equally at load time. That meant slide 1, the one visible on first paint and the one Lighthouse uses to measure LCP, was not prioritised over slides 2 and 3.

The fix was explicit: fetchpriority="high" on slide 1, deferred loading on slides 2 and 3. A useHead preload was added for the default hero image on the homepage route.

Beyond priority, the hero image itself was restructured. WebP derivatives at 480w and 768w, srcset with sizes="100vw", intrinsic dimensions declared, and object-fit set. The browser stopped guessing at layout before the image arrived, which cut the layout shift that was compounding the LCP problem.

WebP conversion was then extended across the site. Product gallery images, search overlay thumbnails, blog article hero images, and collection grids were all moved to WebP via the Shopify CDN srcset helper (shopifyCdnImageUrl). Appropriately sized downloads replaced fixed, over-large PNGs.


Fonts: Removing the Blocking Request

Google Fonts was loading Playfair Display and Lato with more weights than the UI actually used. Each request was a render-blocking round trip to an external host.

The replacement was straightforward. Both typefaces were self-hosted via Fontsource, served as WOFF2, with font-display: swap. The Google Fonts link tag and its associated preconnect were removed entirely.

Unused font weights were trimmed before the migration to keep the self-hosted payload lean. The result was one fewer external dependency and a font delivery path that does not block rendering.


Script Placement, Caching, and the Smaller Wins

The Shopify web components script was moved from the <head> to the end of <body>. This removed it from the parser's critical path without changing its behaviour.

preconnect hints were added for the Shopify CDN and the Storefront API host. The homepage was configured for prerendering in Nitro with Cache-Control headers using s-maxage and stale-while-revalidate, so repeat visitors at the edge receive a cached response almost immediately.

Nuxt DevTools was disabled in production. It contributes bytes and runtime overhead that serve no purpose outside development.

Below-fold homepage sections, specifically the Google Reviews carousel and Instagram Teaser, were lazy-loaded and deferred until after idle via double requestAnimationFrame. The reviews carousel also respects prefers-reduced-motion, which reduces unnecessary animation work on devices where users have opted out of motion.


CLS and CSS: Layout Stability Before Paint

Cumulative Layout Shift penalties were coming from two places: the Best Sellers section and the Reviews area, both of which lacked minimum height declarations. Adding min-height to both removed the shift.

The hero arrow controls were using backdrop-filter for their visual treatment. This is expensive to composite, particularly on mobile. Replacing it with a solid fill achieved the same visual result at a fraction of the compositing cost.

A homepage CSS cleanup removed unused rules, including .btn--secondary. Nitro's compressPublicAssets was enabled to generate Brotli and gzip sidecars for static and prerendered output.

Below-fold images were given decoding="async" to move decode work off the critical path entirely.


The Numbers

Mobile Desktop
Performance (before) 72 82
Performance (after) 89 98
Accessibility 73 73
Best Practices 100 100
SEO 92 92

The 17-point mobile gain and 16-point desktop gain came from addressing LCP (hero, fonts, preload, CDN images, prerender), INP and TBT (deferred sections, script placement, DevTools off, cheaper CSS), CLS (dimensions, min-heights, font strategy), and transfer size (WebP, compression, font subsetting and self-hosting).


What This Approach Generalises To

The pattern is not specific to Nuxt or Shopify. Every production site accumulates third-party scripts, unoptimised images, and font requests that made sense at the time they were added. They compound quietly until someone maps them.

Measure first. Remove what blocks. Defer what is below the fold. Declare dimensions. Self-host fonts. The gains tend to be larger than expected, precisely because no single change is dramatic on its own.


For software architecture, system design, or backend services requirements, contact BaseStation Private Limited at [email protected]. Access further technical articles at blog.base-station.io.

Subscribe to Base-Station Engineering Blog

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]
Subscribe