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.
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 →