---
title: "Core Web Vitals for Vue Applications"
description: "Measure and optimize LCP, INP, and CLS in Vue apps to improve user experience and search rankings."
canonical_url: "https://nuxtseo.com/learn-seo/vue/launch-and-listen/core-web-vitals"
last_updated: "2026-01-29"
---

<key-takeaways>

- Target thresholds: LCP ≤2.5s, INP ≤200ms, CLS ≤0.1
- INP replaced FID in 2024 and is now the primary responsiveness metric
- Lab data (Lighthouse) is insufficient for INP; use RUM tools for field data

</key-takeaways>

[Core Web Vitals](https://developers.google.com/search/docs/appearance/core-web-vitals) became a Google ranking factor in June 2021. The metrics measure loading performance, interactivity, and visual stability. aspects that directly impact user experience and search rankings.

## The Three Metrics

### LCP (Largest Contentful Paint)

LCP measures how long it takes for the largest visible element to render. This is typically a hero image, heading, or large text block.

**Thresholds:**

- Good: ≤ 2.5 seconds
- Needs improvement: 2.5–4.0 seconds
- Poor: > 4.0 seconds

[Google's research](https://web.dev/blog/inp-cwv) shows sites with good LCP see 24% lower bounce rates.

### INP (Interaction to Next Paint)

INP [replaced First Input Delay (FID) in March 2024](https://web.dev/blog/inp-cwv-march-12) as the standard responsiveness metric. Unlike FID, which only measured the first click, **INP measures all interactions throughout the entire page lifecycle** (clicks, taps, keyboard presses).

**Thresholds:**

- Good: ≤ 200 milliseconds
- Needs improvement: 200–500 milliseconds
- Poor: > 500 milliseconds

[Chrome usage data](https://web.dev/blog/inp-cwv) shows users spend 90% of their time on a page after it loads, making responsiveness throughout the session more important than first-interaction metrics.

### CLS (Cumulative Layout Shift)

CLS measures visual stability. Every unexpected layout shift gets scored. images loading without dimensions, fonts causing text reflow, dynamic content insertion.

**Thresholds:**

- Good: ≤ 0.1
- Needs improvement: 0.1–0.25
- Poor: > 0.25

## Vue LCP Optimizations

### Avoid Client-Side Rendering for Content

Pure client-side rendering hurts LCP. The browser downloads JavaScript, parses it, executes it, then renders content. [Vue's official performance guide](https://vuejs.org/guide/best-practices/performance.html) states: "If your use case is sensitive to page load performance, avoid shipping it as a pure client-side SPA."

Use SSR or static site generation for content-heavy pages.

### Preload Critical Assets

Preload the LCP element's resources in your `index.html`:

```html
<head>
  <link rel="preload" href="/hero-image.jpg" as="image">
  <link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>
</head>
```

### Lazy Load Below-Fold Images

Load only what's visible on initial render:

```vue
<template>
  <div>
    <!-- Above-fold: eager loading -->
    <img src="/hero.jpg" alt="Hero" width="1200" height="600">

    <!-- Below-fold: lazy loading -->
    <img
      src="/product-1.jpg"
      alt="Product"
      width="800"
      height="600"
      loading="lazy"
    >
  </div>
</template>
```

Always specify width and height to prevent CLS.

### Optimize Font Loading

Use `font-display: swap` to show system fonts immediately while web fonts load:

```css
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var.woff2') format('woff2');
  font-display: swap;
}
```

### Dynamic Imports for Route Components

[Split your bundle](https://www.debugbear.com/blog/optimize-vue-performance) to load only what's needed:

```ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      component: () => import('./views/Home.vue')
    },
    {
      path: '/dashboard',
      component: () => import('./views/Dashboard.vue')
    }
  ]
})
```

[Vue.js parallel fetching](https://medium.com/@m.kiselyow/vue-js-parallel-fetching-improves-lcp-and-fcp-289f0898badc) improves LCP by loading component files, data, and images simultaneously.

## Vue INP Optimizations

### Debounce Event Handlers

[Debouncing](https://www.debugbear.com/blog/optimize-vue-performance) prevents performance issues from rapid-fire events:

```vue
<script setup lang="ts">
import { ref } from 'vue'

const searchQuery = ref('')
let timeout = null

function handleSearch(event) {
  clearTimeout(timeout)
  timeout = setTimeout(() => {
    searchQuery.value = event.target.value
    // Perform search
  }, 300)
}
</script>

<template>
  <input placeholder="Search..." @input="handleSearch">
</template>
```

### Use v-memo for Large Lists

[v-memo](https://www.bairesdev.com/blog/vue-performance-optimization/) memoizes component output to skip re-renders:

```vue
<template>
  <div v-for="item in items" :key="item.id" v-memo="[item.id, item.selected]">
    {{ item.name }}
  </div>
</template>
```

Re-renders only happen when `item.id` or `item.selected` changes.

### Offload Heavy Computation to Web Workers

Keep the main thread responsive:

```ts
// worker.ts
self.addEventListener('message', (e) => {
  const result = heavyComputation(e.data)
  self.postMessage(result)
})

// component.vue
const worker = new Worker(new URL('./worker.ts', import.meta.url))

worker.postMessage(data)
worker.addEventListener('message', (e) => {
  console.log('Result:', e.data)
})
```

### Optimize Computed Properties

[Computed properties](https://vuejs.org/guide/best-practices/performance.html) cache results. Use them instead of methods for expensive operations:

```vue
<script setup lang="ts">
import { computed, ref } from 'vue'

const items = ref([...largeArray])

// Cached, recalculates only when items changes
const filteredItems = computed(() =>
  items.value.filter(item => item.active)
)
</script>
```

[Breaking long tasks](https://web.dev/articles/optimize-inp#yield_periodically_to_the_main_thread) can reduce INP from 350ms to 120ms.

## Vue CLS Fixes

### Set Image Dimensions

Prevent layout shifts by reserving space:

```vue
<template>
  <img
    src="/product.jpg"
    alt="Product"
    width="800"
    height="600"
  >
</template>
```

Or use aspect ratio with CSS:

```vue
<template>
  <div class="image-container">
    <img src="/product.jpg" alt="Product">
  </div>
</template>

<style scoped>
.image-container {
  aspect-ratio: 16 / 9;
  overflow: hidden;
}

.image-container img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
</style>
```

### Reserve Space for Dynamic Content

Skeleton screens or placeholders prevent shifts:

```vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'

const data = ref(null)
const loading = ref(true)

onMounted(() => {
  (async () => {
    data.value = await fetchData()
    loading.value = false
  })()
})
</script>

<template>
  <div class="content">
    <div v-if="loading" class="skeleton">
      <!-- Same dimensions as actual content -->
    </div>
    <div v-else>
      {{ data }}
    </div>
  </div>
</template>

<style scoped>
.skeleton {
  height: 200px; /* Match real content height */
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
}
</style>
```

### Avoid Flash of Unstyled Content (FOUC)

Preload fonts and use `font-display: swap`:

```html
<head>
  <link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>
</head>
```

```css
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var.woff2') format('woff2');
  font-display: swap;
  size-adjust: 100%; /* Adjust fallback font metrics */
}
```

[Adjusting local system fonts to match web fonts](https://web.dev/articles/fallback-fonts) using fallback metrics reduces CLS.

### Handle Ads and Embeds

Reserve space for third-party content:

```vue
<template>
  <div class="ad-container" style="min-height: 250px;">
    <!-- Ad loads here -->
  </div>
</template>
```

## Measuring Core Web Vitals

### PageSpeed Insights

[PageSpeed Insights](https://pagespeed.web.dev/) shows field data (real user metrics from Chrome UX Report) and lab data (simulated tests).

Field data is what Google uses for rankings. Lab data helps debug specific issues.

### Real User Monitoring (RUM) Tools

Field data is critical for INP. Lab tools (Lighthouse) cannot accurately simulate user interactions. In 2026, relying on RUM tools is standard practice:

- **Pingdom / Datadog**: Enterprise-grade monitoring with detailed INP breakdowns.
- **DebugBear**: Specialized CWV monitoring with deep interaction diagnostics.
- **Sentry**: Tracks frontend performance and correlates slow interactions with code.
- **Cloudflare RUM**: Free basic monitoring for sites on their network.

### Lighthouse

Run Lighthouse in Chrome DevTools (Performance tab) for local testing. It measures LCP, CLS, and provides optimization suggestions.

### Chrome DevTools Performance Panel

Record a session to see frame-by-frame rendering:

1. Open DevTools → Performance
2. Click Record
3. Interact with your page
4. Stop recording
5. View LCP, CLS, and long tasks

[Chrome DevTools](https://developer.chrome.com/docs/devtools/performance/reference#core-web-vitals) shows local LCP and CLS scores instantly. Interact with the page to capture INP.

### web-vitals Library

[Google's web-vitals library](https://github.com/GoogleChrome/web-vitals) sends metrics to your analytics:

```ts
import { onCLS, onINP, onLCP } from 'web-vitals'

onLCP((metric) => {
  console.log('LCP:', metric.value)
  // Send to analytics
  sendToAnalytics({ metric: 'LCP', value: metric.value })
})

onINP((metric) => {
  console.log('INP:', metric.value)
  sendToAnalytics({ metric: 'INP', value: metric.value })
})

onCLS((metric) => {
  console.log('CLS:', metric.value)
  sendToAnalytics({ metric: 'CLS', value: metric.value })
})

function sendToAnalytics({ metric, value }) {
  // Send to Google Analytics, Plausible, etc.
  if (window.gtag) {
    window.gtag('event', metric, { value: Math.round(value) })
  }
}
```

[Capture p75 and p95 metrics](https://madewithvuejs.com/blog/advanced-vue-performance-monitoring) for all critical flows, sliced by device and region.

## Core Web Vitals Impact on Rankings

Google treats Core Web Vitals as a [tiebreaker ranking factor](https://developers.google.com/search/docs/appearance/core-web-vitals). Content relevance is still the primary signal. For queries with multiple relevant results, good page experience can be the differentiator.

[Industry research shows](https://developers.google.com/search/docs/appearance/page-experience) Core Web Vitals account for 10–15% of ranking signals. Only [47% of websites pass all three metrics in 2025](https://httparchive.org/reports/core-web-vitals).

Sites passing all three vitals see:

- 24% lower bounce rates
- 25% higher conversion rates moving from Poor to Good
- 30% revenue improvements from higher engagement

[Mobile-first indexing](https://developers.google.com/search/docs/crawling-indexing/mobile/mobile-first-indexing) now treats Core Web Vitals as indexing requirements rather than just ranking signals. Google evaluates mobile versions of sites primarily.

To pass assessment, [75% of page visits](https://web.dev/articles/defining-core-web-vitals-thresholds) must meet "Good" thresholds.

## Using Nuxt?

Nuxt provides [built-in performance optimizations](https://nuxt.com/docs/4.x/guide/best-practices/performance) including automatic code splitting, image optimization, and hybrid rendering. See our [Nuxt Core Web Vitals guide](/learn-seo/nuxt/launch-and-listen/core-web-vitals) for framework-specific implementations.
