A comprehensive checklist of performance optimizations every Nuxt developer should master.

1. Component Optimization

Use defineAsyncComponent for Code Splitting

Lazy load components that aren't immediately needed:

<script setup>
// Heavy components only load when needed
const HeavyChart = defineAsyncComponent(() => import('~/components/HeavyChart.vue'));
const VideoPlayer = defineAsyncComponent(() => import('~/components/VideoPlayer.vue'));
</script>

<template>
    <div>
        <Suspense>
            <HeavyChart v-if="showChart" />
            <template #fallback>
                <div>Loading chart...</div>
            </template>
        </Suspense>
    </div>
</template>

Impact: Reduces initial bundle size, faster Time to Interactive.

Client-Only Components

Don't render heavy components on the server:

<template>
    <div>
        <ClientOnly>
            <InteractiveMap />
            <template #fallback>
                <div>Loading map...</div>
            </template>
        </ClientOnly>
    </div>
</template>

Use for: Third-party widgets, maps, charts, browser-only APIs.

2. Data Fetching Optimization

Use useFetch with Key

Prevent duplicate requests with proper keys:

const { data, error } = await useFetch('/api/posts', {
    key: 'posts-list',
    lazy: true,
    server: true,
    transform: (data) => data.posts // Only return what you need
});

Lazy Data Fetching

Defer non-critical data:

const { data: criticalData } = await useFetch('/api/critical');

// Lazy load non-critical data
const { data: analytics } = useFetch('/api/analytics', {
    lazy: true,
    server: false // Client-side only
});

Parallel Data Fetching

Fetch multiple resources simultaneously:

const [posts, users, settings] = await Promise.all([
    useFetch('/api/posts'),
    useFetch('/api/users'),
    useFetch('/api/settings')
]);

3. Route Rules & Caching

Static Site Generation (SSG)

Pre-render pages at build time:

// nuxt.config.js
export default defineNuxtConfig({
    routeRules: {
        '/': { prerender: true },
        '/about': { prerender: true },
        '/blog/**': { isr: 3600 } // ISR: Revalidate every hour
    }
});

Stale-While-Revalidate (SWR)

Serve cached content while updating in background:

routeRules: {
    '/api/**': { swr: true },
    '/blog': { swr: 3600 }, // Cache for 1 hour
    '/products/**': { swr: 1800 } // Cache for 30 min
}

Static Asset Caching

Long-term cache for unchanging assets:

routeRules: {
    '/_nuxt/**': { 
        headers: { 
            'cache-control': 'public, max-age=31536000, immutable' 
        } 
    }
}

4. Image Optimization

Use NuxtImg with Lazy Loading

<template>
    <NuxtImg
        src="/images/hero.jpg"
        width="800"
        height="600"
        loading="lazy"
        format="webp"
        quality="80"
        alt="Hero image"
    />
</template>

Configure Image Module

// nuxt.config.js
export default defineNuxtConfig({
    image: {
        formats: ['webp', 'avif'],
        quality: 80,
        screens: {
            xs: 320,
            sm: 640,
            md: 768,
            lg: 1024,
            xl: 1280
        },
        densities: [1, 2]
    }
});

Use NuxtPicture for Art Direction

<template>
    <NuxtPicture
        src="/images/hero.jpg"
        :img-attrs="{ alt: 'Hero' }"
        :modifiers="{ 
            fit: 'cover',
            format: 'webp'
        }"
    />
</template>

5. Bundle Optimization

Analyze Your Bundle

# Build with analysis
npx nuxi analyze

# Or use build command
npm run build

Auto-Import Configuration

Optimize auto-imports to reduce bundle size:

// nuxt.config.js
export default defineNuxtConfig({
    imports: {
        presets: [
            {
                from: 'lodash-es',
                imports: ['debounce', 'throttle']
            }
        ]
    }
});

Tree Shaking for Third-Party Libraries

// Import only what you need
import debounce from 'lodash-es/debounce';

// Not the entire library
// ❌ import _ from 'lodash';

6. Composables for Reusability

Create Performance-Optimized Composables

// composables/useDebounce.js
export const useDebounce = (fn, delay = 300) => {
    let timeoutId;
    
    return (...args) => {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => fn(...args), delay);
    };
};

// Usage
const handleSearch = useDebounce((query) => {
    fetchResults(query);
}, 300);

Lazy Composables

// Only load when needed
const { data } = await useLazyAsyncData('heavy-data', () => 
    $fetch('/api/heavy-data')
);

7. Payload Optimization

Reduce Payload Size

// nuxt.config.js
export default defineNuxtConfig({
    experimental: {
        payloadExtraction: true,
        renderJsonPayloads: true,
        treeshakeClientOnly: true
    }
});

Data Transformation in Queries

Transform data on the server to reduce payload:

const { data } = await useFetch('/api/posts', {
    transform: (posts) => {
        // Only return what the UI needs
        return posts.map(({ id, title, excerpt }) => ({
            id,
            title,
            excerpt
        }));
    }
});

8. Resource Hints & Preloading

DNS Prefetch & Preconnect

// nuxt.config.js
app: {
    head: {
        link: [
            // DNS prefetch for third parties
            { rel: 'dns-prefetch', href: 'https://cdn.example.com' },
            // Preconnect for critical resources
            { 
                rel: 'preconnect', 
                href: 'https://fonts.googleapis.com',
                crossorigin: 'anonymous'
            }
        ]
    }
}

Preload Critical Assets

app: {
    head: {
        link: [
            {
                rel: 'preload',
                as: 'font',
                type: 'font/woff2',
                href: '/fonts/main.woff2',
                crossorigin: 'anonymous'
            }
        ]
    }
}

Prefetch Routes

<template>
    <!-- Prefetch on hover -->
    <NuxtLink to="/about" prefetch>About</NuxtLink>
    
    <!-- No prefetch for external links -->
    <NuxtLink to="/heavy-page" :prefetch="false">Heavy Page</NuxtLink>
</template>

9. State Management Performance

Use Pinia with Smart Caching

// stores/posts.js
export const usePostsStore = defineStore('posts', {
    state: () => ({
        posts: [],
        lastFetch: null
    }),
    actions: {
        async fetchPosts() {
            // Cache for 5 minutes
            if (this.lastFetch && Date.now() - this.lastFetch < 300000) {
                return this.posts;
            }
            
            const data = await $fetch('/api/posts');
            this.posts = data;
            this.lastFetch = Date.now();
            return data;
        }
    }
});

Avoid Reactivity for Large Datasets

// Use shallowRef for large arrays/objects
const largeDataset = shallowRef([]);

// Or mark as raw if no reactivity needed
const staticData = markRaw(hugeDataset);

10. Server-Side Optimization

Use Server Routes for Heavy Logic

// server/api/process.post.js
export default defineEventHandler(async (event) => {
    const body = await readBody(event);
    
    // Heavy processing on server
    const result = await heavyProcessing(body);
    
    return result;
});

Nitro Presets for Performance

// nuxt.config.js
export default defineNuxtConfig({
    nitro: {
        preset: 'vercel', // or 'netlify', 'cloudflare'
        compressPublicAssets: true,
        minify: true
    }
});

11. Hybrid Rendering Strategies

Per-Route Rendering

routeRules: {
    '/': { prerender: true }, // SSG
    '/blog/**': { swr: 3600 }, // ISR
    '/api/**': { cors: true }, // API routes
    '/admin/**': { ssr: false } // CSR only
}

Choose wisely:

  • SSG (prerender): Static content, rarely changes
  • SSR: Dynamic, personalized content
  • ISR (swr): Semi-static, periodic updates
  • CSR (ssr: false): Client-only, requires auth

12. Critical CSS & Fonts

Inline Critical CSS

// nuxt.config.js
export default defineNuxtConfig({
    experimental: {
        inlineSSRStyles: true
    }
});

Font Optimization

// Use font-display: swap
app: {
    head: {
        link: [
            {
                rel: 'stylesheet',
                href: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap'
            }
        ]
    }
}

13. Third-Party Script Management

Use useHead with Script

useHead({
    script: [
        {
            src: 'https://analytics.example.com/script.js',
            defer: true,
            // Or use async: true
        }
    ]
});

Lazy Load Analytics

// plugins/analytics.client.js
export default defineNuxtPlugin(() => {
    // Only load on client, after page load
    if (typeof window !== 'undefined') {
        window.addEventListener('load', () => {
            // Initialize analytics
        });
    }
});

14. Build-Time Optimization

Reduce Build Time

// nuxt.config.js
export default defineNuxtConfig({
    vite: {
        build: {
            rollupOptions: {
                output: {
                    manualChunks(id) {
                        // Split vendor chunks
                        if (id.includes('node_modules')) {
                            return 'vendor';
                        }
                    }
                }
            }
        }
    }
});

Enable TypeScript Performance Mode

// nuxt.config.js
export default defineNuxtConfig({
    typescript: {
        typeCheck: 'build', // Only on build, not during dev
        strict: true
    }
});

15. Database & API Optimization

Use Caching Headers

// server/api/posts.get.js
export default defineEventHandler(async (event) => {
    const posts = await fetchPosts();
    
    // Set cache headers
    setResponseHeaders(event, {
        'cache-control': 'public, max-age=3600, s-maxage=7200'
    });
    
    return posts;
});

Implement API Response Caching

// server/utils/cache.js
const cache = new Map();

export const getCached = async (key, fetchFn, ttl = 3600000) => {
    const cached = cache.get(key);
    
    if (cached && Date.now() - cached.timestamp < ttl) {
        return cached.data;
    }
    
    const data = await fetchFn();
    cache.set(key, { data, timestamp: Date.now() });
    
    return data;
};

16. Monitoring & Debugging

Use Nuxt DevTools

// nuxt.config.js
export default defineNuxtConfig({
    devtools: {
        enabled: true,
        timeline: {
            enabled: true
        }
    }
});

Performance Profiling

// Use console.time for profiling
const start = performance.now();
await heavyOperation();
console.log(`Operation took ${performance.now() - start}ms`);

Monitor Core Web Vitals

// plugins/web-vitals.client.js
export default defineNuxtPlugin(() => {
    if (process.client) {
        import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
            getCLS(console.log);
            getFID(console.log);
            getFCP(console.log);
            getLCP(console.log);
            getTTFB(console.log);
        });
    }
});

17. Nuxt 3 Specific Features

Auto-Import Optimization

Nuxt auto-imports are tree-shakable:

// Automatically available, tree-shaken
const route = useRoute();
const router = useRouter();
const { data } = await useFetch('/api/data');

Tip: Verify imports are being tree-shaken in build output.

Nitro Server Engine

Leverage Nitro's built-in features:

// nuxt.config.js
export default defineNuxtConfig({
    nitro: {
        compressPublicAssets: true, // Gzip/Brotli
        minify: true,
        prerender: {
            crawlLinks: true,
            routes: ['/sitemap.xml']
        }
    }
});

UseAsyncData for Complex Fetching

const { data, pending, refresh } = await useAsyncData(
    'complex-data',
    async () => {
        const [posts, users] = await Promise.all([
            $fetch('/api/posts'),
            $fetch('/api/users')
        ]);
        
        return { posts, users };
    },
    {
        lazy: true,
        server: true,
        watch: [someRef] // Re-fetch when ref changes
    }
);

18. CSS & Style Optimization

Scoped Styles with Deep Selectors

<style scoped>
.parent {
    color: blue;
}

/* Use :deep() for child components */
.parent :deep(.child) {
    color: red;
}
</style>

Extract Critical CSS

// nuxt.config.js
export default defineNuxtConfig({
    experimental: {
        inlineSSRStyles: true
    }
});

Use CSS Variables for Theming

// Faster than JavaScript theme switching
:root {
    --color-primary: #ff6b35;
    --color-text: #333;
}

.button {
    background: var(--color-primary);
    color: var(--color-text);
}

19. Middleware & Guards

Optimize Middleware Execution

// middleware/auth.js
export default defineNuxtRouteMiddleware((to, from) => {
    // Skip if already authenticated
    if (process.server && to.meta.public) {
        return;
    }
    
    const user = useAuthUser();
    if (!user.value) {
        return navigateTo('/login');
    }
});

Use Global vs Route Middleware Wisely

// Global middleware runs on every route (use sparingly)
// middleware/analytics.global.js

// Route-specific middleware (better performance)
definePageMeta({
    middleware: ['auth', 'analytics']
});

20. Error Handling Performance

Custom Error Pages

<!-- error.vue -->
<script setup>
const props = defineProps({
    error: Object
});

// Don't fetch unnecessary data on error pages
useMeta({ 
    title: 'Error',
    description: 'An error occurred'
});
</script>

Graceful Degradation

const { data, error } = await useFetch('/api/data', {
    onRequestError() {
        // Handle network errors
        return { fallback: true };
    },
    onResponseError() {
        // Handle API errors
        return { fallback: true };
    }
});

Performance Checklist

Before deploying:

  • Run npm run analyze to check bundle size
  • All images use loading="lazy" and modern formats
  • Critical routes are prerendered or use SWR
  • Heavy components are code-split with defineAsyncComponent
  • API responses have proper cache headers
  • Third-party scripts load asynchronously
  • Fonts use font-display: swap
  • No console.log in production code
  • Lighthouse score > 90 for all pages
  • Test on slow 3G network
  • Check payload sizes in Network tab
  • Verify Core Web Vitals (LCP < 2.5s, FID < 100ms, CLS < 0.1)

Common Nuxt Performance Mistakes

1. Over-fetching Data

// ❌ Bad - Fetching entire objects
const { data } = await useFetch('/api/posts');

// ✅ Good - Transform to get only what's needed
const { data } = await useFetch('/api/posts', {
    transform: (posts) => posts.map(({ id, title, excerpt }) => ({
        id, title, excerpt
    }))
});

2. Not Using Proper Keys

// ❌ Bad - No key, might duplicate requests
const { data } = await useFetch('/api/posts');

// ✅ Good - With unique key
const { data } = await useFetch('/api/posts', {
    key: 'posts-list'
});

3. Blocking the Critical Path

// ❌ Bad - Waits for both before rendering
const posts = await useFetch('/api/posts');
const comments = await useFetch('/api/comments');

// ✅ Good - Render while loading comments
const posts = await useFetch('/api/posts');
const { data: comments } = useFetch('/api/comments', { lazy: true });

4. Not Utilizing Server Routes

// ❌ Bad - Heavy logic in component
const processData = (rawData) => {
    // Expensive operations
    return processed;
};

// ✅ Good - Process on server
// server/api/process.post.js
export default defineEventHandler(async (event) => {
    const data = await readBody(event);
    return processData(data);
});

5. Ignoring Hydration Mismatch

// ❌ Bad - Different content server vs client
<div>{{ new Date().toLocaleString() }}</div>

// ✅ Good - Consistent or client-only
<ClientOnly>
    <div>{{ new Date().toLocaleString() }}</div>
    <template #fallback>
        <div>Loading...</div>
    </template>
</ClientOnly>

Advanced Nuxt Performance Patterns

1. Intersection Observer for Lazy Loading

const { data } = await useLazyAsyncData('lazy-content', async () => {
    return $fetch('/api/content');
}, {
    server: false // Client-side only
});

// Trigger when element is visible
const target = ref(null);
const { stop } = useIntersectionObserver(target, ([{ isIntersecting }]) => {
    if (isIntersecting) {
        // Fetch data
        refresh();
        stop();
    }
});

2. Incremental Static Regeneration (ISR)

routeRules: {
    '/blog/**': {
        isr: 3600, // Revalidate every hour
        // Or
        swr: 3600 // Stale-while-revalidate
    }
}

3. Edge Rendering

Deploy to edge for lower latency:

// nuxt.config.js
export default defineNuxtConfig({
    nitro: {
        preset: 'vercel-edge',
        // Or 'cloudflare-pages', 'netlify-edge'
    }
});

4. Streaming SSR

For large pages, stream HTML chunks:

// Automatically enabled in Nuxt 3
// Just ensure async components use Suspense

Testing Performance

Local Testing

# Build for production
npm run build

# Preview production build
npm run preview

# Test with throttling in Chrome DevTools
# Network tab → Slow 3G

Lighthouse CI

Add to your CI/CD pipeline:

# Install
npm install -g @lhci/cli

# Run
lhci autorun --collect.url=http://localhost:3000

Real User Monitoring

// Track actual user performance
export default defineNuxtPlugin(() => {
    if (process.client) {
        window.addEventListener('load', () => {
            const perfData = performance.getEntriesByType('navigation')[0];
            console.log('Load time:', perfData.loadEventEnd - perfData.fetchStart);
        });
    }
});

Performance Budgets

Set and enforce performance budgets:

// lighthouse-budget.json
{
    "resourceSizes": [
        {
            "resourceType": "script",
            "budget": 300
        },
        {
            "resourceType": "image",
            "budget": 500
        }
    ]
}

Nuxt 3 vs Nuxt 2 Performance Wins

Nuxt 3 improvements out of the box:

  • ⚡ Nitro engine (faster builds, smaller bundles)
  • 🎯 Native ES modules (better tree-shaking)
  • 📦 Auto code splitting
  • 🔥 Faster HMR in development
  • 💾 Smaller bundle sizes (~50% reduction)
  • 🚀 Edge-ready by default

Key Takeaways

  1. Measure first - Use Chrome DevTools, Lighthouse, and Nuxt DevTools
  2. Use route rules - SWR and ISR are your friends
  3. Code split aggressively - Every heavy component should be async
  4. Optimize images - Use NuxtImg with modern formats
  5. Cache strategically - Server, CDN, and browser caching
  6. Server-side processing - Move heavy logic to server routes
  7. Monitor continuously - Track real user metrics

Resources

Remember

Test on real devices, real networks, and real scenarios. Your users will thank you.


Last updated: October 2025