404 Not Found

404 Not Found


nginx

Performance Optimization

MegaShop is now live, but its Lighthouse score is only 45—it takes 4.5 seconds to load the first screen, the images aren't optimized, and the JS bundle is too large. Charlie needs a Lighthouse score of 90+ and an LCP of less than 2 seconds for the first screen to stay competitive.

1. What You'll Learn


2. A True Story of an Architect

(1) Pain Point: Lighthouse score of 45, 4.5 seconds to first content paint

Alice opened MegaShop on her phone and it took 4.5 seconds for the products to appear. Charlie ran a Lighthouse audit and found that the JS bundle was 2.8 MB, all images were uncompressed PNGs, there were no cache headers, and the code for 50 components loaded on the first screen.

(2) Solutions for Nuxt 3 Performance Optimization

Nuxt 3 includes a variety of built-in performance optimization features:

TYPESCRIPT
// Image optimization with @nuxt/image
<NuxtImg src="/products/123.webp" width="400" height="400" loading="lazy" />

(3) Performance: Lighthouse score of 92, LCP of 1.3 seconds

After optimization, the JS bundle size was reduced to 180 KB (gzip), images are automatically served as WebP and are responsive, LCP for the first screen is 1.3 seconds, and the Lighthouse score is 92.


3. Code Splitting and Tree Shaking

(1) Performance Optimization Decision Tree

100%
flowchart TB
    A[Performance Issue] --> B{What type?}
    B -->|Slow first paint| C[Code Splitting]
    B -->|Large images| D[Image Optimization]
    B -->|Slow subsequent loads| E[Caching Strategy]
    B -->|JS too large| F[Tree Shaking]

    C --> C1[Lazy components]
    C --> C2[Dynamic imports]
    D --> D1[@nuxt/image module]
    D --> D2[WebP conversion]
    E --> E1[HTTP Cache Headers]
    E --> E2[Nitro KV Cache]
    F --> F1[Check bundle with rollup-plugin-visualizer]

(2) Bundle Analysis

BASH
npm install -D rollup-plugin-visualizer
TYPESCRIPT
// nuxt.config.ts
import { visualizer } from 'rollup-plugin-visualizer'

export default defineNuxtConfig({
  vite: {
    plugins: [
      visualizer({ filename: 'bundle-stats.html', open: true })
    ],
    build: {
      rollupOptions: {
        output: {
          manualChunks: {
            'vendor': ['vue', 'vue-router'],
            'pinia': ['pinia']
          }
        }
      }
    }
  }
})

(1) ▶ Example: Lazy Component Code Splitting

VUE
<template>
  <div>
    <!-- Core: loaded immediately -->
    <AppHeader />
    <ProductHero :product="featured" />

    <!-- Below fold: lazy loaded -->
    <LazyProductGrid :products="all" />
    <LazyProductReviews :product-id="id" />

    <!-- Conditional: loaded only when visible -->
    <LazyNewsletterSignup v-if="showNewsletter" />
  </div>
</template>

Output:

TEXT
// Execution Successful

(3) Before-and-After Comparison of the Optimization

Metric Before Optimization After Optimization Improvement
JS Bundle (gzip) 2.8 MB 180 KB -94%
Number of components on the first screen 50 8 -84%
JS Download on First Screen 2.8 MB 85 KB -97%
LCP 4.5s 1.3s -71%

4. Image Optimization

(1) ▶ Example: Installing @nuxt/image

BASH
npm install @nuxt/image

Output:

TEXT
# Command executed successfully
TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/image'],
  image: {
    quality: 80,
    format: ['webp', 'avif'],
    screens: {
      xs: 320, sm: 640, md: 768, lg: 1024, xl: 1280, xxl: 1536
    }
  }
})

(2) ▶ Example: NuxtImg Reactive Images

VUE
<template>
  <div>
    <!-- Auto WebP/AVIF + responsive srcset -->
    <NuxtImg
      src="/images/product-123.jpg"
      width="800"
      height="600"
      format="webp"
      loading="lazy"
      sizes="sm:100vw md:50vw lg:33vw"
      :modifiers="{ quality: 80 }"
      alt="Premium Headphones"
    />

    <!-- Placeholder with blur-up -->
    <NuxtImg
      src="/images/hero.jpg"
      placeholder
      width="1920"
      height="1080"
      format="webp"
      alt="MegaShop Hero"
    />
  </div>
</template>

Output:

TEXT
// Execution Successful

(3) ▶ Example: NuxtPicture Multi-Format

VUE
<template>
  <!-- NuxtPicture: auto-select best format per browser -->
  <NuxtPicture
    src="/images/banner.jpg"
    width="1200"
    height="400"
    format="avif,webp"
    loading="lazy"
    alt="Sale Banner"
  />
</template>

Output:

TEXT
// Execution Successful

(1) Comparison of Image Formats

Format Compression Ratio Browser Support Use Cases
AVIF 🟢 Best (50% vs. JPG) Chrome/Edge/Firefox Modern browsers
WebP 🟢 High (30% vs. JPG) All major browsers General-purpose preferred format
JPEG 🟡 Benchmark All Compatibility Fallback
PNG 🔴 Large All Transparent Images

5. Caching Strategy

(1) ▶ Example: HTTP Cache Headers

TYPESCRIPT
// nuxt.config.ts - Cache headers per route
export default defineNuxtConfig({
  routeRules: {
    // Static assets: 1 year immutable cache
    '/_nuxt/**': {
      headers: { 'cache-control': 'public, max-age=31536000, immutable' }
    },
    // Images: 1 day cache
    '/images/**': {
      headers: { 'cache-control': 'public, max-age=86400, stale-while-revalidate=604800' }
    },
    // Product pages: ISR cache
    '/products/**': {
      swr: 86400,
      headers: { 'cache-control': 'public, s-maxage=86400, stale-while-revalidate=3600' }
    }
  }
})

Output:

TEXT
// Execution Successful

(2) ▶ Example: Nitro KV Cache API

TYPESCRIPT
// server/api/products/[id].get.ts - Cached handler
export default cachedEventHandler(
  async (event) => {
    const id = getRouterParam(event, 'id')
    const product = await fetchProductFromDB(Number(id))
    return product
  },
  {
    maxAge: 60 * 60,        // Cache for 1 hour
    swr: true,               // Serve stale while revalidating
    getKey: (event) => `product:${getRouterParam(event, 'id')}`,
    varies: ['Accept-Language'] // Different cache per language
  }
)

Output:

TEXT
// Execution Successful

(1) Comparison of Cache Hierarchies

Level Location Speed Control Method
Browser Cache User Device ⚡⚡⚡ Fastest Cache-Control Header
CDN Edge Caching Nearest Node ⚡⚡ Fast CDN Configuration + Vary
Nitro KV Cache Server Memory ⚡ Fast cachedEventHandler
ISR Page Cache Nitro Storage ⚡ Fast routeRules swr

6. Comprehensive Example: MegaShop Performance Optimization Configuration

TYPESCRIPT
// nuxt.config.ts - Full performance optimization
export default defineNuxtConfig({
  ssr: true,

  modules: ['@nuxt/image', '@nuxtjs/tailwindcss'],

  // Image optimization
  image: {
    quality: 80,
    format: ['webp', 'avif'],
    screens: { xs: 320, sm: 640, md: 768, lg: 1024, xl: 1280 }
  },

  // Vite build optimization
  vite: {
    build: {
      cssCodeSplit: true,
      rollupOptions: {
        output: {
          manualChunks(id) {
            if (id.includes('node_modules')) {
              if (id.includes('pinia')) return 'vendor-pinia'
              if (id.includes('vue')) return 'vendor-vue'
              return 'vendor-libs'
            }
          }
        }
      }
    }
  },

  // Route-level caching strategy
  routeRules: {
    '/': { prerender: true },
    '/products': { swr: 3600 },
    '/products/**': { swr: 86400 },
    '/admin/**': { ssr: false },
    '/_nuxt/**': { headers: { 'cache-control': 'public, max-age=31536000, immutable' } },
    '/images/**': { headers: { 'cache-control': 'public, max-age=86400' } }
  },

  // App performance hints
  experimental: {
    payloadExtraction: true // Reduce payload size
  }
})
VUE
<!-- pages/index.vue - Optimized homepage -->
<template>
  <div>
    <AppHeader />

    <!-- Above fold: eager load -->
    <section class="hero">
      <NuxtImg src="/images/hero.jpg" width="1200" height="400" format="webp"
        priority alt="MegaShop" />
      <h1>MegaShop</h1>
    </section>

    <!-- Below fold: lazy load -->
    <LazyProductGrid :products="featured" />

    <!-- Conditional: only load when in viewport -->
    <LazyNewsletterSignup v-if="showNewsletter" />
  </div>
</template>

<script setup lang="ts">
const { data: featured } = await useFetch('/api/products/featured')
const showNewsletter = ref(false)
</script>

❓ FAQ

Q What should I do if my Lighthouse score is low?
A Address the issues by priority: 1) Image optimization (WebP/compression/lazy loading) → 2) JS bundle analysis (lazy components/Tree Shaking) → 3) Caching strategy (HTTP headers/ISR) → 4) Font optimization.
Q Does @nuxt/image support external image URLs?
A Yes, it does. Configure image.domains to add external domains: image: { domains: ['cdn.example.com'] }. NuxtImg will automatically optimize external images.
Q What is the difference between NuxtImg and a regular img tag?
A NuxtImg automatically generates srcset, converts images to WebP/AVIF, crops them to fit the device's dimensions, and enables lazy loading. With a regular img tag, you need to handle all these optimizations manually.
Q Which takes precedence: ISR caching or HTTP caching?
A ISR is server-side caching (stored in Nitro), while HTTP caching occurs at the browser/CDN layer. Request flow: Browser cache → CDN cache → Nitro ISR → dynamic rendering. Each layer operates independently.
Q What are some pitfalls to watch out for when configuring manualChunks?
A Do not include server-side code in client chunks. Nuxt 3 automatically handles the separation of server and client code. manualChunks should only be used to split third-party libraries.
Q Is it hard to achieve a Lighthouse score of 90+?
A You can reach 90+ with SSR, image optimization, and a caching strategy. The hardest part is keeping LCP under 2.5 seconds—you must streamline the critical rendering path and load only essential resources on the first screen.

📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty: ⭐): Install @nuxt/image, replace all img tags with NuxtImg, and verify the automatic WebP conversion.
  2. Advanced Exercise (Difficulty: ⭐⭐): Use rollup-plugin-visualizer to analyze the bundle, identify the largest dependency, and configure manualChunks to split it.
  3. Challenge (Difficulty: ⭐⭐⭐): Achieve a Lighthouse score of 90+: Image optimization + code splitting + caching strategies, using Lighthouse CI for automated audits

---|

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏