12/18/2025

Nuxt App Speed: A Complete A-Z Optimization Guide

Nuxt App Speed: A Complete A-Z Optimization Guide

Web application performance isn't just a technical metric. It directly impacts conversion rates, SEO, and user satisfaction. Nuxt provides a powerful arsenal of optimization tools, but they need to be applied correctly.

In this article, we'll break down all the key aspects of Nuxt application speed—from built-in mechanisms to advanced techniques.

What is Performance in the Context of Nuxt?

Google evaluates site performance through Core Web Vitals—three key metrics:

MetricWhat it MeasuresTarget Value
LCP (Largest Contentful Paint)Time to load the largest element< 2.5s
CLS (Cumulative Layout Shift)Visual stability< 0.1
INP (Interaction to Next Paint)Responsiveness to user interaction< 200ms

These metrics directly influence Google rankings. Nuxt is optimized to achieve high scores out of the box, but real-world results depend on proper configuration.

Important: INP replaced FID (First Input Delay) in March 2024 as an official Core Web Vitals metric.

Built-in Nuxt Optimization Mechanisms

Nuxt automatically applies a series of optimizations without any extra configuration.

Automatic Code Splitting

Each page and asynchronous component is automatically split into separate chunks. The user only downloads the necessary code:

// Nuxt automatically splits into chunks:
// - pages/index.vue → _nuxt/index-[hash].js
// - pages/about.vue → _nuxt/about-[hash].js
// - components/Heavy.vue → _nuxt/Heavy-[hash].js (if lazy)

Tree-shaking and Vite

Nuxt uses Vite as its build tool. Vite provides:

  • Tree-shaking—removing unused code
  • Automatic CSS extraction—separate files for better caching
  • Minification—reducing bundle size

The <NuxtLink> component automatically prefetches pages when the link enters the viewport:

<template>
  <!-- Prefetch will trigger automatically on entering the viewport -->
  <NuxtLink to="/products">Catalog</NuxtLink>
  
  <!-- Disable prefetch for a specific link -->
  <NuxtLink to="/heavy-page" :prefetch="false">
    Heavy Page
  </NuxtLink>
</template>

Rendering Modes and routeRules

Nuxt supports hybrid rendering—different strategies for different routes. This is one of the most powerful optimization tools.

Available Modes

ModeDescriptionWhen to Use
prerenderGenerate HTML at build timeStatic content (homepage, about us)
swrStale-While-RevalidateFrequently updated content
isrIncremental Static RegenerationContent with CDN caching
ssr: falseClient-side onlyAdmin panels, dashboards

Configuration Example

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // Static homepage
    '/': { prerender: true },
    
    // Catalog: 1-hour cache, background refresh
    '/products/**': { swr: 3600 },
    
    // Blog: CDN cache with ISR
    '/blog/**': { isr: true },
    
    // Admin: client-side only
    '/admin/**': { ssr: false },
    
    // API: CORS + cache
    '/api/**': { 
      cors: true,
      cache: { maxAge: 60 }
    }
  }
})

Tip: Use defineRouteRules directly within a page component for local configuration.

Lazy Loading and Lazy Hydration

These two techniques are often confused, but they solve different problems.

Lazy Loading Components

This defers the loading of a component's code until it's needed:

<script setup>
const showModal = ref(false)
</script>

<template>
  <button @click="showModal = true">Open</button>
  
  <!-- The code will only be loaded when showModal = true -->
  <LazyModal v-if="showModal" @close="showModal = false" />
</template>

Just add the Lazy prefix to the component name—Nuxt does the rest.

Lazy Hydration (with Nuxt 3.16+)

This defers the hydration (activation of interactivity) of an already-loaded component. The HTML is rendered on the server, but the JavaScript is activated later.

Available strategies:

<template>
  <!-- Hydrate when it enters the viewport -->
  <LazyFooter hydrate-on-visible />
  
  <!-- Hydrate when the browser is idle -->
  <LazySidebar hydrate-on-idle />
  
  <!-- Hydrate on interaction -->
  <LazyComments hydrate-on-interaction="click" />
  
  <!-- Hydrate on a media query -->
  <LazyMobileMenu hydrate-on-media-query="(max-width: 768px)" />
  
  <!-- Never hydrate (purely static content) -->
  <LazyStaticBanner hydrate-never />
</template>

Important: Lazy hydration significantly improves Time to Interactive (TTI), especially for components below the viewport.

Server Components and Islands

For content that requires no interactivity at all, use Server Components:

<!-- components/RenderMarkdown.server.vue -->
<script setup>
const props = defineProps<{ source: string }>()
</script>

<template>
  <div v-html="renderMarkdown(props.source)" />
</template>

The .server.vue extension means:

  • The component is rendered only on the server
  • Its JavaScript is not sent to the client
  • Hydration does not occur

An alternative is <NuxtIsland> for wrapping any component:

<template>
  <NuxtIsland name="HeavyChart" :props="{ data }" />
</template>

Image Optimization

Unoptimized images are the main cause of poor LCP. Nuxt Image provides a comprehensive solution.

Installation and Basic Usage

npx nuxi module add @nuxt/image
<template>
  <!-- Instead of <img> -->
  <NuxtImg 
    src="/hero.jpg"
    width="1200"
    height="600"
    alt="Hero image"
    format="webp"
    loading="lazy"
  />
  
  <!-- For an LCP image -->
  <NuxtImg 
    src="/hero.jpg"
    width="1200"
    height="600"
    alt="Hero image"
    :loading="undefined"
    fetchpriority="high"
  />
</template>

Key Practices

<template>
  <!-- ❌ Bad: lazy for LCP -->
  <NuxtImg src="/hero.jpg" loading="lazy" />
  
  <!-- ✅ Good: eager + fetchpriority for LCP -->
  <NuxtImg 
    src="/hero.jpg" 
    loading="eager" 
    fetchpriority="high"
  />
  
  <!-- ✅ Good: responsive with sizes -->
  <NuxtImg 
    src="/product.jpg"
    sizes="(max-width: 640px) 100vw, 50vw"
    :modifiers="{ format: 'webp', quality: 80 }"
  />
</template>
PropertyFor LCPFor Others
loadingeager or not specifiedlazy
fetchpriorityhighlow or not specified
formatwebp or avifwebp or avif

Font Optimization

Fonts affect CLS and LCP. Nuxt Fonts automates optimization.

How Nuxt Fonts Works

npx nuxi module add @nuxt/fonts

The module automatically:

  1. Finds font-family in your CSS
  2. Downloads fonts from Google/Bunny/other providers
  3. Proxies and caches them locally
  4. Generates fallback metrics to reduce CLS
  5. Includes them in the build with long-term caching
/* Nuxt Fonts will process this automatically */
body {
  font-family: 'Inter', sans-serif;
}

Fallback Metrics

Nuxt Fonts uses fontaine to calculate metrics for a system fallback font that closely matches the web font:

/* Generated automatically */
@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
  size-adjust: 107%;
}

This minimizes the text "jump" when the web font loads.

Bundle Analysis and Reduction

The size of the JavaScript bundle is critical for LCP and TTI.

Bundle Analysis

# Visualize chunk sizes
npx nuxi analyze

This command generates an interactive treemap showing the size of all modules.

Common Problems and Solutions

Problem: Importing an entire library

// ❌ Bad: the whole lodash (~70KB)
import _ from 'lodash'

// ✅ Good: only the needed function (~4KB)
import debounce from 'lodash/debounce'

Problem: Heavy libraries without lazy loading

<script setup>
// ❌ Bad: loaded immediately
import { Chart } from 'chart.js'

// ✅ Good: dynamic import
const { Chart } = await import('chart.js')
</script>

Problem: Duplicate dependencies

# Check for duplicates
npm ls moment
npm ls date-fns

Vite Configuration for Optimization

// nuxt.config.ts
export default defineNuxtConfig({
  vite: {
    build: {
      rollupOptions: {
        output: {
          manualChunks: {
            // Move large libraries to separate chunks
            'vendor-chart': ['chart.js'],
            'vendor-editor': ['@tiptap/core', '@tiptap/vue-3']
          }
        }
      }
    }
  }
})

Performance Measurement Tools

Nuxt DevTools

Built-in tools for local development:

  • Timeline—component render times
  • Assets—file sizes
  • Render Tree—component relationships
  • Inspect—execution time of each file
// nuxt.config.ts
export default defineNuxtConfig({
  devtools: { enabled: true }
})

Chrome DevTools

TabWhat it Shows
PerformanceLCP, CLS, INP in real-time
LighthouseComprehensive audit with recommendations
NetworkResource loading waterfall

PageSpeed Insights

For production testing, use PageSpeed Insights. It shows:

  • Real user data from Chrome (CrUX)
  • Lab data from Lighthouse
  • Specific recommendations

Common Mistakes and How to Avoid Them

1. Lazy Loading the LCP Element

<!-- ❌ Critical mistake -->
<NuxtImg src="/hero.jpg" loading="lazy" />

<!-- ✅ Correct -->
<NuxtImg src="/hero.jpg" fetchpriority="high" />

The LCP element must be loaded with the highest priority.

2. Excessive Reactivity

// ❌ Bad: everything is reactive
const data = ref({
  config: { ... },  // Never changes
  items: []         // Changes
})

// ✅ Good: only what's needed is reactive
const CONFIG = { ... }  // A regular constant
const items = ref([])   // A reactive array

3. Memory Leaks

// ❌ Bad: event listener without cleanup
onMounted(() => {
  window.addEventListener('resize', handleResize)
})

// ✅ Good: with cleanup
onMounted(() => {
  window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
  window.removeEventListener('resize', handleResize)
})

4. Too Many Plugins

Every plugin is executed during hydration. Consider alternatives:

  • Composables instead of plugins for local logic
  • Lazy-loaded modules for heavy libraries
  • Server-only plugins for server-side logic

5. Blocking Third-Party Scripts

// ❌ Bad: blocks rendering
useHead({
  script: [{ src: 'https://analytics.com/script.js' }]
})

// ✅ Good: use Nuxt Scripts
// npx nuxi module add @nuxt/scripts

Nuxt Scripts loads third-party scripts with minimal impact on performance.

Optimization Checklist

Before deploying, check the following:

  • nuxi analyze—bundle < 250KB per page
  • LCP image does not have loading="lazy"
  • Lazy hydration for below-the-fold components
  • routeRules for static pages
  • Nuxt Image for all images
  • Nuxt Fonts for web fonts
  • Lighthouse Performance score > 90

Sources

  1. Nuxt Performance Best Practices — official Nuxt documentation
  2. Lazy Hydration and Server Components in Nuxt — Vue School
  3. Nuxt Image Documentation — official module documentation
  4. Nuxt Rendering Modes — official documentation
  5. Announcing Nuxt 4.0 — official Nuxt blog