Performance Optimization

Server Components, Lazy Loads, payload Tricks

Performance Optimization

Default Nuxt is fast — the wins come from being deliberate about hydration, bundle splitting, and what runs on the client at all.

5 min read Level 3/5 #nuxt#performance#hydration
What you'll learn
  • Use server-only components for static slots
  • Lazy-load heavy components with the Lazy prefix
  • Tune Nitro caching via routeRules

A blank Nuxt app already scores 100 on Lighthouse. Real apps slip when they ship megabytes of JS, hydrate static markup, or refetch data on every navigation. Four tools fix most cases.

Lazy-Load Heavy Components

Prefix any auto-imported component with Lazy and Nuxt splits it into its own chunk, loading only when the component is first rendered.

<template>
  <button @click="show = true">Open editor</button>
  <LazyRichTextEditor v-if="show" />
</template>

<script setup lang="ts">
const show = ref(false)
</script>

The 400KB editor bundle never downloads until the user clicks the button.

Server Components

Components named Foo.server.vue render on the server only — they never ship JS, and their HTML is streamed into the page. Perfect for marketing slots, footers, syntax-highlighted docs.

<!-- components/Footer.server.vue -->
<template>
  <footer>{{ year }} · {{ commitSha }}</footer>
</template>

<script setup lang="ts">
const year = new Date().getFullYear()
const commitSha = process.env.GIT_SHA
</script>

Used in templates as <Footer />. Zero hydration cost.

routeRules — Per-Route Caching

nuxt.config.ts lets you set caching, prerendering, and headers per path.

export default defineNuxtConfig({
  routeRules: {
    '/':              { prerender: true },           // static at build
    '/blog/**':       { swr: 3600 },                 // 1h stale-while-revalidate
    '/dashboard/**':  { ssr: false },                // SPA mode (no SSR)
    '/api/feed':      { isr: 60 },                   // ISR every 60s
  },
})

Mix and match by route — marketing pages prerender, the dashboard ships as SPA.

Avoid Expensive Computeds

computed re-runs whenever a reactive dep changes. A heavy formatter inside a list v-for item runs N times per keystroke.

// Slow — runs on every keystroke
const formatted = computed(() => items.value.map(heavyFormat))

// Better — cache by item
const formattedMap = new Map<string, string>()
const formatted = computed(() => items.value.map((i) => {
  if (!formattedMap.has(i.id)) formattedMap.set(i.id, heavyFormat(i))
  return formattedMap.get(i.id)!
}))

Analyze the Bundle

nuxi build && nuxi analyze

Opens a treemap of every chunk. Look for surprise imports — moment, lodash, a chart library bundled into the homepage.

Nuxt DevTools →