Skip to content

Core Web Vitals with Static Nuxt: Getting LCP, INP and CLS Right

How statically generated Nuxt sites reach good Core Web Vitals: images, fonts, hydration, lazy-loaded 3D scenes and measuring the right way.

by Alexander HerlingPublished 7 min read

Abstract illustration: three gauge arcs in light and deep blue, each needle in the good range, in front of soft clouds

Core Web Vitals are the three metrics Google uses to assess the user experience of loading and interacting with a page. They feed into search, but more importantly they measure what visitors actually feel. Statically generated Nuxt sites start from an excellent position. Here is where the typical bottlenecks still hide and how to fix them, including pages with interactive 3D elements.

The three metrics

MetricMeasuresGoodNeeds improvementPoor
LCP (Largest Contentful Paint)When the largest visible element has rendered≤ 2.5 s≤ 4 s> 4 s
INP (Interaction to Next Paint)How quickly the page responds to input≤ 200 ms≤ 500 ms> 500 ms
CLS (Cumulative Layout Shift)How much the layout shifts unexpectedly≤ 0.1≤ 0.25> 0.25

Each is assessed at the 75th percentile of real-user page loads, separately for mobile and desktop. INP replaced the earlier FID metric in March 2024 and is stricter, because it considers every interaction during a visit, not just the first.

Why static Nuxt has a head start

With nuxt generate or prerendered routes, the build produces finished HTML for every page. A CDN serves it without a server computing anything per request. Response times are short, and content is in the HTML from the start, which also helps search engines and AI crawlers.

The browser then loads JavaScript and hydrates the page, making it interactive. That is where many Nuxt sites run into trouble: too much JavaScript, executed too early.

LCP: show the largest element fast

The LCP element is usually a hero image or a large heading. To get it on screen early:

Optimise images. @nuxt/image generates appropriate sizes and modern formats such as WebP and AVIF at build time. Use sizes to state how wide the image actually is in the layout, so phones do not download desktop images.

Prioritise the hero image. It must not be lazy-loaded; give it high priority instead:

<NuxtImg
  src="/hero.jpg"
  sizes="100vw md:1200px"
  width="1200"
  height="630"
  format="webp"
  preload
  fetchpriority="high"
  alt="…"
/>

For AVIF with a WebP fallback, <NuxtPicture> is the better fit. Every image below the fold gets loading="lazy".

Keep render-blocking resources small. Critical CSS should be lean, and third-party scripts do not belong in the document head.

Set caching correctly. Files under /_nuxt/ carry a content hash in their name and can be cached as immutable for a year. HTML should be cached briefly or revalidated so changes arrive quickly.

Fonts without shifts or delays

Web fonts affect LCP and CLS at the same time. The @nuxt/fonts module helps with both: it downloads and self-hosts fonts instead of loading them from third parties, and it generates adjusted fallback fonts with matching metrics. Text appears immediately in a system font that takes up almost the same space, so very little moves when the web font arrives.

More rules:

  • Load only the weights you actually use. Variable fonts often replace several files.
  • Limit character sets to the languages you need (subsetting).
  • Preload the most important font, not all of them.

Self-hosting has a side benefit: no data flows to third parties, which also helps with cookie banners and consent.

INP: less JavaScript, executed later

INP measures the time between an input and the next visible response. It suffers when the main thread is busy, for example while a large component hydrates.

Load components lazily. With the Lazy prefix, Nuxt loads a component's code only when it renders. Since Nuxt 3.16 you can also control when a component hydrates:

<LazyTestimonials hydrate-on-visible />
<LazyNewsletterForm hydrate-on-interaction />
<LazyFooterMap hydrate-never />

Content that is only displayed and needs no interaction does not have to hydrate at all.

Tame third-party scripts. Chat widgets, tag managers and video players are frequent INP culprits. Load them only after an interaction or when the browser is idle. The @nuxt/scripts module provides triggers and placeholder facades for this.

Break up long tasks. Heavy work after a click should show the visible response first and do the rest afterwards, by splitting it into smaller chunks or moving it into a web worker.

CLS: reserve space

Layout shifts happen when content claims space after the fact.

  • Give images and videos width and height, or an aspect-ratio.
  • Embed maps or videos inside fixed-size placeholders.
  • Show banners, notices and consent dialogs as overlays at the edge, not pushed in above the content.
  • Animate transform and opacity only, not top, height or margin.

3D without sacrificing load time

Interactive 3D scenes built with Three.js or TresJS can define a website. The libraries are large, though, and WebGL taxes the CPU and GPU. Here is how the page stays fast anyway:

  1. Static preview first. An optimised image of the scene appears immediately and is the LCP element.
  2. Load 3D code separately. The scene lives in its own component, included via <ClientOnly> and Lazy, so Three.js stays out of the main bundle.
  3. Start late. Load it when the scene scrolls into view or the user interacts.
  4. Compress assets. glTF models with Draco or Meshopt compression, textures in KTX2.
  5. Limit rendering. Cap the pixel ratio at 2, pause off-screen scenes and skip animation under prefers-reduced-motion.
  6. Fallback. On weak devices or without WebGL, the preview image stays in place.

Measure the right way

There are two kinds of data:

  • Lab data from Lighthouse or Chrome DevTools: reproducible and good for debugging, but under simulated conditions.
  • Field data from real users in the Chrome User Experience Report (CrUX): the basis of Search Console's assessment, evaluated over a rolling 28-day window. Improvements therefore take a few weeks to show fully.

For quicker feedback, collect the metrics yourself, for example with the web-vitals library and privacy-friendly reporting. Always test on an average mid-range phone, not just on a developer laptop.

Getting to good scores

Open the Core Web Vitals report in Search Console and see which metric is poor on which page groups. Usually a single cause affects many pages: an oversized hero image, a font, a third-party script. Fix that one cause first. If you are planning a new Nuxt site or want to speed up an existing one, we can help as part of our web and app development services.

Frequently asked questions

Are statically generated pages automatically fast?

They have a big head start because ready-made HTML is served without server computation. Large images, late-loading fonts or heavy JavaScript can still drag the scores down.

Why does PageSpeed Insights show different values from Search Console?

PageSpeed Insights shows field data from real users at the top and a lab test under simulated conditions below. Search Console uses field data only, grouped by similar pages over 28 days.

Does a 3D scene inevitably hurt Core Web Vitals?

Not if it is loaded correctly. A static preview image becomes the largest element, and the 3D library is loaded only once the scene is visible or the user interacts.