A transição do Nuxt 2 para o Nuxt 3 representa uma reescrita arquitetural completa, impulsionada pelo Vite, Vue 3 e o motor Nitro. Os benefícios práticos incluem bundles de produção significativamente menores, Hot Module Replacement (HMR) instantâneo no desenvolvimento, adoção nativa da Composition API, separação clara entre as camadas de cliente e servidor, e substituição do Vuex pelo Pinia.
Devido às mudanças profundas no core do framework, o comando npx nuxi upgrade não é recomendado. A abordagem correta e mais segura é inicializar um novo projeto Nuxt 3 e realizar a migração manual dos módulos, configurações e componentes.
Configurações e Estrutura de Diretórios
O arquivo nuxt.config.ts sofre alterações significativas. A sintaxe CommonJS (module.exports) é substituída por ES Modules (export default).
Reorganização de Diretórios e Metadados
É altamente recomendado isolar o código-fonte em um diretório dedicado, como application/, configurando o srcDir. As configurações globais de <head> foram movidas para o objeto app, e as configurações de ambiente foram unificadas no runtimeConfig.
// nuxt.config.ts
export default defineNuxtConfig({
srcDir: 'application/',
app: {
head: {
title: 'Minha Aplicação',
charset: 'utf-8',
viewport: 'width=device-width, initial-scale=1',
meta: [
{ name: 'description', content: 'Aplicação migrada para Nuxt 3' }
]
}
},
runtimeConfig: {
apiSecret: process.env.API_SECRET,
public: {
apiBase: process.env.API_BASE_URL || '/api'
}
}
})
Proxy de Desenvolvimento com Nitro
O módulo @nuxtjs/proxy foi descontinuado. O Nuxt 3 utiliza o motor Nitro para gerenciar proxies. Note que o devProxy opera estritamente em ambiente de desenvolvimento.
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
devProxy: {
'/graphql': {
target: 'https://api.staging.internal',
changeOrigin: true,
headers: { 'X-Forwarded-Host': 'localhost' }
}
}
}
})
Camada de Front-end: Layouts, Componentes e Composables
A estrutura de rotas e componentes permanece baseada no sistema de arquivos, mas a sintaxe dos componentes deve ser atualizada para o Vue 3.
Definição de Layouts
A definição de layouts agora é feita via macro definePageMeta dentro do bloco <script setup>.
<script setup>
definePageMeta({
layout: 'admin-dashboard',
})
</script>
Assets e Arquivos Estáticos
O diretório assets/ continua destinado a arquivos processados pelo bundler (CSS, SCSS, imagens importadas). O diretório public/ serve arquivos estáticos brutos (como favicon.ico ou robots.txt) diretamente na raiz do servidor. Para importar imagens no JavaScript ou templates, utilize a sintaxe import em vez de require.
Composables (Auto-importação)
O diretório composables/ é nativo do Nuxt 3. Funções exportadas neste diretório são automaticamente injetadas no contexto da aplicação, eliminando a necessidade de imports explícitos em componentes, layouts ou plugins.
// application/composables/useCurrency.ts
type CurrencyFormatter = (value: number, locale: string) => string;
export const formatCurrency: CurrencyFormatter = (value, locale) => {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: 'BRL'
}).format(value);
};
Uso direto no componente:
<script setup>
const price = formatCurrency(1599.90, 'pt-BR');
</script>
Plugins e Controle de SSR
Em vez de confgiurar a execução SSR no nuxt.config, o Nuxt 3 utiliza sufixos de arquivo. Adicione .client.ts para executar apenas no cliente, ou .server.ts para apenas no servidor.
// application/plugins/leaflet.client.ts
import L from 'leaflet';
export default defineNuxtPlugin(() => {
return {
provide: {
mapEngine: L
}
};
});
Gerenciamento de Estado com Pinia
O Vuex não é mais integrado. O Pinia é a biblioteac oficial, oferecendo uma API mais limpa, suporte nativo ao TypeScript e eliminação da distinção entre mutations e actions.
// application/stores/cart.ts
import { defineStore } from 'pinia';
interface CartItem {
id: string;
quantity: number;
}
export const useCartStore = defineStore('cart', {
state: () => ({
items: [] as CartItem[],
discount: 0
}),
getters: {
totalItems: (state) => state.items.reduce((sum, item) => sum + item.quantity, 0)
},
actions: {
addProduct(productId: string) {
const existing = this.items.find(i => i.id === productId);
if (existing) {
existing.quantity++;
} else {
this.items.push({ id: productId, quantity: 1 });
}
}
}
});
Camada de Servidor e Motor Nitro
A maior mudança arquitetural é a introdução do diretório server/, que isola completamente a lógica de backend, rotas, middleware e plugins do Nitro.
Endpoints de API
Arquivos dentro de server/api/ expõem endpoints HTTP. O handler utiliza defineEventHandler.
// server/api/health.ts
export default defineEventHandler(() => {
return {
status: 'operational',
timestamp: Date.now()
};
});
Roteamento e Proxy Avançado
Para rotas que não se enquadram em /api, ou para criar proxies complexos, utilize o diretório server/routes/ combinado com as utilidades do pacote h3.
// server/routes/analytics/[...path].ts
import { sendProxy } from 'h3';
export default defineEventHandler(async (event) => {
const targetBase = 'https://metrics.external-platform.com';
const targetUrl = new URL(event.node.req.url || '', targetBase);
return await sendProxy(event, targetUrl.toString());
});
Middleware de Servidor
O middleware em server/middleware/ intercepta todas as requisições antes de chegarem aos handlers. É ideal para injeção de headers, logging estruturado ou validação de tokens.
// server/middleware/requestLogger.ts
import crypto from 'crypto';
export default defineEventHandler((event) => {
const requestId = crypto.randomUUID();
event.node.res.setHeader('X-Request-Id', requestId);
console.debug(`[${requestId}] ${event.method} ${event.path} - ${new Date().toISOString()}`);
});
Integração com $fetch e Variáveis de Ambiente
O Nuxt 3 desencoraja o uso direto do Axios no servidor, favorecendo a utilitária global $fetch (baseada no ofetch), que integra automaticamente com o runtimeConfig.
// server/api/users/index.ts
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
const userData = await $fetch('/v1/users', {
baseURL: config.public.apiBase,
headers: {
'Authorization': `Bearer ${config.apiSecret}`
}
});
return userData;
});