| Nuxt 5 production optimization: hydration, performance, testing with Vitest, deployment to Cloudflare/Vercel/Netlify, and migration from Nuxt 4. Use when: debugging hydration mismatches, optimizing performance and Core Web Vitals, writing tests with Vitest, deploying to Cloudflare Pages/Workers/Vercel/Netlify, or migrating from Nuxt 4 to Nuxt 5.
SKILL.md
Nuxt 5 Production Guide
Hydration, performance, testing, deployment, and migration patterns.
What's New in Nuxt 5
v5 Key Changes
Change
Nuxt 4
Nuxt 5
Bundler
Vite 6 (esbuild + Rollup)
Vite 8 (Rolldown)
Server engine
Nitro v2
Nitro v3 (h3 v2)
Server errors
createError({statusCode})
HTTPError({status})
Client-only placeholder
Empty <div>
HTML comment node
callHook
Always returns Promise
May return void
clearNuxtState
Sets to undefined
Resets to initial default
Page names
Auto-generated
Normalized to route names
JSX support
Included by default
Optional (on-demand)
externalVue
Configurable
Removed (always mocked)
Client-Only Comment Placeholders (v5)
Client-only components (.client.vue files and createClientOnly() wrappers) now render an HTML comment on the server instead of an empty <div>. This fixes scoped styles hydration issues.
<!-- If you relied on the placeholder <div> for layout -->
<ClientOnly>
<MyComponent />
<template #fallback>
<div class="placeholder" style="min-height: 200px"></div>
</template>
</ClientOnly>
// Before
routeRules: {
'/old': { redirect: { to: '/new', statusCode: 302 } }
}
// After
routeRules: {
'/old': { redirect: { to: '/new', status: 302 } }
}
Step 7: Update Import Paths
// Before
import { defineEventHandler, getQuery } from 'h3'
// After
import { defineEventHandler, getQuery } from 'nitro/h3'
// Or rely on auto-imports (no import needed)
Step 8: Remove Deprecated Options
// Remove these from nuxt.config.ts
export default defineNuxtConfig({
experimental: {
externalVue: false, // REMOVED - delete this
viteEnvironmentApi: true, // REMOVED - always enabled
}
})
Step 9: Install JSX Plugin (If Needed)
# Only if your project uses .jsx/.tsx files
bun add -D @vitejs/plugin-vue-jsx
Step 10: Update callHook Usage
// Before
nuxtApp.callHook('my:hook', data).then(() => { ... })
// After
await nuxtApp.callHook('my:hook', data)
Common Anti-Patterns
Client-Only Code on Server
// WRONG
const width = window.innerWidth
// CORRECT
if (import.meta.client) {
const width = window.innerWidth
}
// Or use onMounted
onMounted(() => {
const width = window.innerWidth
})
Non-Deterministic SSR
// WRONG
const id = Math.random()
const time = Date.now()
// CORRECT
const id = useState('id', () => Math.random())
const time = useState('time', () => Date.now())
Troubleshooting
Hydration Mismatch:
Check for window, document, localStorage usage
Wrap in ClientOnly or use onMounted
Look for Math.random(), Date.now()
Check if relying on <div> placeholder for client-only components
Build Errors:
rm -rf .nuxt .output node_modules/.vite && bun install
Vite Plugin Warnings:
Migrate from extendViteConfig({ server }) to configEnvironment
Use applyToEnvironment instead of server: false / client: false
Rolldown Build Issues:
Replace rollupOptions with rolldownOptions
Replace vite.esbuild with vite.oxc
Check CJS interop changes in Vite 8
Related Skills
nuxt-core: Project setup, routing, configuration
nuxt-data: Composables, data fetching, state
nuxt-server: Server routes, API patterns (Nitro v3)
cloudflare-d1: D1 database patterns
Version: 5.0.0 | Last Updated: 2026-03-30 | License: MIT