Receber por email
← Voltar para o catálogo

nuxt-production

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

178estrelas
Atualizado há 5 meses

Ver no GitHub ↗ Licença: MIT

Como adicionar

/plugin marketplace add secondsky/claude-skills

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/nuxt-production-secondsky/svg)](https://www.skillteca.com.br/skills/nuxt-production-secondsky)

Alerta por categoria

Receba novas skills de Design e Frontend toda segunda

1 email curto, só com as skills novas de Design e Frontend. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

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>

To revert to the old <div> behavior:

// nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    clientNodePlaceholder: false
  }
})

Non-Async callHook (v5)

callHook may now return void instead of always returning Promise. Always use await:

// WRONG
nuxtApp.callHook('my:hook', data).then(() => { ... })

// CORRECT
await nuxtApp.callHook('my:hook', data)

When to Load References

Load references/hydration.md when:

  • Debugging "Hydration node mismatch" errors
  • Implementing ClientOnly components
  • Fixing non-deterministic rendering issues

Load references/performance.md when:

  • Optimizing Core Web Vitals scores
  • Implementing lazy loading and code splitting
  • Reducing bundle size

Load references/testing-vitest.md when:

  • Writing component tests with @nuxt/test-utils
  • Testing composables with Nuxt context
  • Mocking Nuxt APIs

Load references/deployment-cloudflare.md when:

  • Deploying to Cloudflare Pages or Workers
  • Configuring wrangler.toml
  • Setting up NuxtHub integration

Hydration Best Practices

What Causes Hydration Mismatches

| Cause | Example | Fix | |-------|---------|-----| | Non-deterministic values | Math.random() | Use useState | | Browser APIs on server | window.innerWidth | Use onMounted | | Date/time on server | new Date() | Use useState or ClientOnly | | Third-party scripts | Analytics | Use ClientOnly |

Fix Patterns

<!-- Non-deterministic values -->
<script setup>
const id = useState('random-id', () => Math.random())
</script>

<!-- Browser APIs -->
<script setup>
const width = ref(0)
onMounted(() => {
  width.value = window.innerWidth
})
</script>

<!-- ClientOnly component -->
<template>
  <ClientOnly>
    <MyMapComponent />
    <template #fallback>
      <div class="skeleton">Loading map...</div>
    </template>
  </ClientOnly>
</template>

Performance Optimization

Lazy Loading Components

<script setup>
const HeavyChart = defineAsyncComponent(() =>
  import('~/components/HeavyChart.vue')
)
</script>

Lazy Hydration

<script setup>
const LazyComponent = defineLazyHydrationComponent(
  'visible',
  () => import('./HeavyComponent.vue')
)

const InteractiveComponent = defineLazyHydrationComponent(
  'interaction',
  () => import('./InteractiveComponent.vue')
)

const IdleComponent = defineLazyHydrationComponent(
  'idle',
  () => import('./IdleComponent.vue')
)
</script>

Route Caching

export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true },
    '/about': { prerender: true },
    '/blog/**': { swr: 3600 },
    '/products/**': { isr: 3600 },
    '/dashboard/**': { ssr: false },
    '/static/**': {
      headers: { 'Cache-Control': 'public, max-age=31536000' }
    }
  }
})

Image Optimization

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

  <NuxtPicture
    src="/images/product.jpg"
    alt="Product"
    sizes="sm:100vw md:50vw lg:400px"
    :modifiers="{ quality: 80 }"
  />
</template>

Testing with Vitest

Setup

bun add -d @nuxt/test-utils vitest @vue/test-utils happy-dom
// vitest.config.ts
import { defineVitestConfig } from '@nuxt/test-utils/config'

export default defineVitestConfig({
  test: {
    environment: 'nuxt',
    environmentOptions: {
      nuxt: {
        domEnvironment: 'happy-dom'
      }
    }
  }
})

Component Testing

import { describe, it, expect } from 'vitest'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import UserCard from '~/components/UserCard.vue'

describe('UserCard', () => {
  it('renders user name', async () => {
    const wrapper = await mountSuspended(UserCard, {
      props: {
        user: { id: 1, name: 'John Doe', email: 'john@example.com' }
      }
    })

    expect(wrapper.text()).toContain('John Doe')
  })
})

Mocking Composables

import { mockNuxtImport } from '@nuxt/test-utils/runtime'

mockNuxtImport('useFetch', () => {
  return () => ({
    data: ref({ users: [{ id: 1, name: 'John' }] }),
    pending: ref(false),
    error: ref(null)
  })
})

Deployment

Cloudflare Pages (Recommended)

bun run build
bunx wrangler pages deploy .output/public
export default defineNuxtConfig({
  nitro: { preset: 'cloudflare_pages' }
})

Cloudflare Workers

export default defineNuxtConfig({
  nitro: { preset: 'cloudflare_module' }
})

Vercel / Netlify

// Vercel
export default defineNuxtConfig({
  nitro: { preset: 'vercel' }
})

// Netlify
export default defineNuxtConfig({
  nitro: { preset: 'netlify' }
})

NuxtHub (Cloudflare All-in-One)

bun add @nuxthub/core
export default defineNuxtConfig({
  modules: ['@nuxthub/core'],
  hub: {
    database: true,
    kv: true,
    blob: true,
    cache: true
  }
})

Migration from Nuxt 4

Step 1: Update package.json

{
  "devDependencies": {
    "nuxt": "^5.0.0"
  }
}

Step 2: Enable Compatibility Mode

// nuxt.config.ts
export default defineNuxtConfig({
  future: {
    compatibilityVersion: 5
  }
})

Step 3: Update Server Error Handling

// Before (Nuxt 4)
import { createError } from 'h3'
throw createError({ statusCode: 404, statusMessage: 'Not Found' })

// After (Nuxt 5)
import { HTTPError } from 'nitro/h3'
throw new HTTPError({ status: 404, statusText: 'Not Found' })

Step 4: Update Server Event API

// Before (Nuxt 4)
const path = event.path
event.node.res.statusCode = 200
setResponseHeader(event, 'x-custom', 'value')
const config = useRuntimeConfig(event)

// After (Nuxt 5)
const path = event.url.pathname
event.res.status = 200
event.res.headers.set('x-custom', 'value')
const config = useRuntimeConfig()

Step 5: Update Vite Config

// Before (Nuxt 4)
export default defineNuxtConfig({
  vite: {
    build: {
      rollupOptions: { ... }
    }
  }
})

// After (Nuxt 5) - use rolldownOptions
export default defineNuxtConfig({
  vite: {
    build: {
      rolldownOptions: { ... }
    }
  }
})

Step 6: Update Route Rules

// 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 D

Compartilhar X LinkedIn