[Lv3] Otimização de performance no Nuxt 3: Bundle Size, velocidade SSR e otimização de imagens
Guia completo de otimização de performance no Nuxt 3: desde redução de Bundle Size, otimização de velocidade SSR até estratégias de carregamento de imagens, criando uma experiência de performance máxima.
1. Eixo principal da resposta na entrevista
- Otimização de Bundle Size: análise (
nuxi analyze), divisão (SplitChunks), Tree Shaking, Lazy Loading. - Otimização de velocidade SSR (TTFB): cache Redis, Nitro Cache, redução de chamadas API bloqueantes, Streaming SSR.
- Otimização de imagens:
@nuxt/image, formato WebP, CDN, Lazy Loading. - Otimização de grandes volumes de dados: Virtual Scrolling, Infinite Scroll, Pagination.
2. Como reduzir o Bundle Size do Nuxt 3?
2.1 Ferramentas de diagnóstico
Primeiro, é necessário saber onde está o gargalo. Use nuxi analyze para visualizar a estrutura do Bundle.
npx nuxi analyze
Isso gera um relatório mostrando quais pacotes ocupam mais espaço.
2.2 Estratégias de otimização
1. Code Splitting (divisão de código)
O Nuxt 3 já faz Code Splitting baseado em rotas (Route-based) por padrão. Mas para pacotes grandes (como ECharts, Lodash), precisamos otimizar manualmente.
Configuracao do Nuxt (Vite/Webpack):
// nuxt.config.ts
export default defineNuxtConfig({
vite: {
build: {
rollupOptions: {
output: {
manualChunks(id) {
// Dividir pacotes grandes de node_modules
if (id.includes('node_modules')) {
if (id.includes('lodash')) return 'lodash';
if (id.includes('echarts')) return 'echarts';
}
},
},
},
},
},
});
2. Tree Shaking e importação sob demanda
Garanta que apenas os módulos necessários sejam importados, não o pacote inteiro.
// Errado: importar todo o lodash
import _ from 'lodash';
_.debounce(() => {}, 100);
// Correto: importar apenas debounce
import debounce from 'lodash/debounce';
debounce(() => {}, 100);
// Recomendado: usar vueuse (específico para Vue e tree-shakable)
import { useDebounceFn } from '@vueuse/core';
3. Lazy Loading de componentes
Para componentes não necessários na primeira tela, use o prefixo Lazy para importação dinâmica.
<template>
<div>
<!-- O código do componente só sera carregado quando show for true -->
<LazyHeavyComponent v-if="show" />
</div>
</template>
4. Remover pacotes desnecessários do Server-side
Garanta que pacotes usados apenas no Server (como drivers de banco de dados, operações de fs) não sejam empacotados no Client. O Nuxt 3 trata automaticamente arquivos terminados em .server.ts, ou use o diretório server/.
3. Como otimizar a velocidade SSR (TTFB)?
3.1 Por que o TTFB é tao longo?
TTFB (Time To First Byte) e o indicador-chave de performance SSR. As causas comuns de TTFB longo são:
- API lenta: o Server precisa esperar a resposta da API do back-end antes de renderizar o HTML.
- Requisicoes sequenciais: múltiplas requisições de API executadas em série, não em paralelo.
- Computacao pesada: o Server executa muitas tarefas CPU-intensive.
3.2 Soluções de otimização
1. Server-Side Caching (Nitro Cache)
Use a funcionalidade de cache do Nitro para armazenar respostas de API ou resultados de renderização.
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// Página inicial com cache de 1 hora (SWR: Stale-While-Revalidate)
'/': { swr: 3600 },
// Páginas de produtos com cache de 10 minutos
'/products/**': { swr: 600 },
// Cache de API
'/api/**': { cache: { maxAge: 60 } },
},
});
2. Requisicoes em paralelo (Parallel Fetching)
Use Promise.all para enviar múltiplas requisições em paralelo, em vez de await uma após a outra.
// Lento: execução sequencial (tempo total = A + B)
const { data: user } = await useFetch('/api/user');
const { data: posts } = await useFetch('/api/posts');
// Rapido: execução paralela (tempo total = Max(A, B))
const [{ data: user }, { data: posts }] = await Promise.all([
useFetch('/api/user'),
useFetch('/api/posts'),
]);
3. Adiamento de dados não críticos (Lazy Fetching)
Dados não necessários na primeira tela podem ser carregados no Client (lazy: true), evitando bloqueio do SSR.
// Dados de comentários não precisam de SEO, podem ser carregados no Client
const { data: comments } = await useFetch('/api/comments', {
lazy: true,
server: false, // Nem mesmo executar no Server
});
4. Streaming SSR (experimental)
O Nuxt 3 suporta HTML Streaming, permitindo renderizar e enviar simultaneamente para que o usuário veja o conteúdo mais rápido.
4. Otimização de imagens no Nuxt 3
4.1 Usando @nuxt/image
O módulo oficial @nuxt/image é a melhor solução, oferecendo:
- Conversao automática de formato: automaticamente para WebP/AVIF.
- Redimensionamento automático: gera imagens no tamanho adequado conforme a tela.
- Lazy Loading: lazy loading integrado.
- Integracao CDN: suporta Cloudinary, Imgix e outros Providers.
4.2 Exemplo de implementação
npm install @nuxt/image
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/image'],
image: {
// Opções padrão
format: ['webp'],
},
});
<template>
<!-- Converte automaticamente para webp, largura 300px, com lazy load ativado -->
<NuxtImg
src="/hero.jpg"
format="webp"
width="300"
loading="lazy"
placeholder
/>
</template>
5. Paginação e rolagem para grandes volumes de dados
5.1 Seleção de solução
Para grandes volumes de dados (ex: 10.000 produtos), existem três estratégias principais, considerando SEO:
| Estratégia | Cenário adequado | Compatibilidade com SEO |
|---|---|---|
| Paginação tradicional (Pagination) | Listas de e-commerce, listas de artigos | Excelente (melhor) |
| Scroll infinito (Infinite Scroll) | Feeds sociais, galerias de fotos | Baixa (requer tratamento especial) |
| Virtual Scroll | Relatorios complexos, listas muito longas | Muito baixa (conteúdo não está no DOM) |
5.2 Como manter SEO com scroll infinito?
Com scroll infinito, motores de busca geralmente rastreiam apenas a primeira página. Soluções:
- Combinar com paginação: fornecer tags
<link rel="next" href="...">para que os crawlers saibam que ha próxima página. - Noscript Fallback: fornecer uma versão de paginação tradicional em
<noscript>para crawlers. - Botao "Carregar mais": SSR renderiza os primeiros 20 registros; cliques subsequentes em "Carregar mais" ou rolagem acionam Client-side fetch.
5.3 Exemplo de implementação (Load More + SEO)
<script setup>
// Dados da primeira tela (SSR)
const page = ref(1);
const { data: posts } = await useFetch('/api/posts', {
query: { page: page.value }
});
// Carregar mais no Client
const loadMore = async () => {
page.value++;
const newPosts = await $fetch('/api/posts', {
query: { page: page.value }
});
posts.value.push(...newPosts);
};
</script>
<template>
<div>
<div v-for="post in posts" :key="post.id">{{ post.title }}</div>
<button @click="loadMore">Carregar mais</button>
<!-- Otimização SEO: informar crawlers sobre próxima página -->
<Head>
<Link rel="next" :href="`/posts?page=${page + 1}`" />
</Head>
</div>
</template>
6. Lazy Loading em ambiente SSR
6.1 Descrição do problema
Em ambiente SSR, se usar IntersectionObserver para implementar Lazy Loading, como o Server não possui window ou document, isso causara erros ou Hydration Mismatch.
6.2 Soluções
1. Usar componentes nativos do Nuxt
<LazyComponent><NuxtImg loading="lazy">
2. Diretiva personalizada (com tratamento SSR)
// plugins/lazy-load.ts
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.directive('lazy', {
mounted(el, binding) {
// Executar apenas no Client
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
el.src = binding.value;
observer.disconnect();
}
});
observer.observe(el);
},
getSSRProps(binding) {
// Renderizar placeholder ou imagem original no Server (conforme necessidade de SEO)
return {
src: 'placeholder.png'
};
}
});
});
7. Monitoramento e rastreamento de performance SSR
7.1 Por que é necessário monitorar?
Os gargalos de performance de aplicações SSR geralmente ocorrem no Server, invisível ao DevTools do navegador. Sem monitoramento, é difícil identificar se API lenta, Memory Leak ou CPU alta são a causa do TTFB lento.
7.2 Ferramentas comuns
-
Nuxt DevTools (fase de desenvolvimento):
- Integrado ao Nuxt 3.
- Visualizar tempo de resposta de Server Routes.
- Preview de Open Graph para SEO.
- Painel Server Routes para monitorar tempo de chamadas de API.
-
Lighthouse / PageSpeed Insights (após deploy):
- Monitorar Core Web Vitals (LCP, CLS, FID/INP).
- LCP (Largest Contentful Paint) depende fortemente do TTFB do SSR.
-
Server-Side Monitoring (APM):
- Sentry / Datadog: rastrear erros e performance do Server.
- OpenTelemetry: rastrear o Request Trace completo (Nuxt Server -> API Server -> DB).
7.3 Implementação de rastreamento simples de tempo
No server/middleware, você pode implementar um timer simples:
// server/middleware/timing.ts
export default defineEventHandler((event) => {
const start = performance.now();
event.node.res.on('finish', () => {
const duration = performance.now() - start;
console.log(`[${event.method}] ${event.path} - ${duration.toFixed(2)}ms`);
// Também pode adicionar header Server-Timing para visualização no DevTools do navegador
// event.node.res.setHeader('Server-Timing', `total;dur=${duration}`);
});
});
8. Resumo para entrevista
P: Como rastrear e monitorar problemas de performance SSR?
Na fase de desenvolvimento, uso principalmente o Nuxt DevTools para verificar tempos de resposta e tamanho do Payload dos Server Routes. Em ambiente de produção, acompanho Core Web Vitals (especialmente LCP) e TTFB. Para investigacao profunda de gargalos no Server, uso Server Middleware customizado para registrar tempos de requisição, envio dos dados via header
Server-Timingao navegador, ou integro Sentry / OpenTelemetry para rastreamento de cadeia completa.
P: Como reduzir o bundle size do Nuxt 3?
Primeiro analiso com
nuxi analyze. Para pacotes grandes (como lodash), aplico Tree Shaking ou divisão manual (manualChunks). Para componentes não essenciais na primeira tela, uso<LazyComponent>para importação dinâmica.
P: Como otimizar a velocidade SSR?
O foco e reduzir o TTFB. Uso
routeRulesdo Nitro para configurar Server-side caching (SWR). Requisicoes de API são paralelizadas comPromise.all. Dados não críticos são configurados comlazy: truepara carregamento no Client.
P: Como fazer otimização de imagens?
Uso o módulo
@nuxt/image, que converte automaticamente para WebP, redimensiona automaticamente, e suporta Lazy Loading, reduzindo significativamente o volume de transferência.
P: Como manter SEO com scroll infinito?
Scroll infinito não é amigavel para SEO. Se for um site de conteúdo, priorizo paginação tradicional. Se scroll infinito for obrigatório, renderizo a primeira página com SSR e uso Meta Tags (
rel="next") para informar crawlers sobre a estrutura de paginação, ou forneco links de paginação via Noscript.