O Problema que Encontrei
Quando assumi o projeto, o Lighthouse marcava 61 em performance, 72 em acessibilidade e 68 em SEO. Era uma landing page de alto tráfego; cada ponto perdido se traduzia em taxa de rejeição maior e menos conversões.
Depois de semanas de análise com o DevTools, PageSpeed Insights e WebPageTest, identifiquei três culpados principais:
- Imagens não otimizadas consumindo 3,2 MB no carregamento inicial
- JavaScript de terceiros bloqueando a renderização por 2,3 segundos
- Fontes carregando de forma síncrona, atrasando drasticamente o LCP
1. Imagens: Do JPG Bruto ao WebP Otimizado
O primeiro passo foi migrar todas as imagens para o componente next/image. Não é apenas sobre compressão: é sobre lazy loading automático, dimensionamento responsivo e conversão automática para WebP/AVIF.
// ❌ Antes
<img src="/hero.jpg" width="1200" height="600" alt="Hero" />
// ✅ Depois
import Image from "next/image";
<Image
src="/hero.jpg"
width={1200}
height={600}
alt="Hero da página principal"
priority // apenas para imagens above-the-fold
quality={85}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
Resultado: redução de 3,2 MB para 680 KB na transferência total de imagens. LCP caiu de 4,8s para 1,9s.
2. Fontes: Eliminando o FOUT com next/font
Trocar o <link> do Google Fonts pelo next/font eliminou o FOUT (Flash of Unstyled Text) e removeu uma requisição DNS externa, que bloqueava o render por até 400ms em conexões lentas.
// app/layout.tsx
import { Inter, Plus_Jakarta_Sans } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
display: "swap", // crucial para CLS
variable: "--font-inter",
});
const jakarta = Plus_Jakarta_Sans({
subsets: ["latin"],
display: "swap",
variable: "--font-jakarta",
});
O display: "swap" garante que o texto seja exibido com uma fonte de fallback enquanto a fonte customizada carrega, eliminando o CLS gerado pelo FOUT.
3. JavaScript de Terceiros: Deferindo o que Não É Crítico
Google Analytics, HotJar e widgets de chat estavam sendo carregados de forma síncrona, bloqueando o parser do navegador. A solução foi a estratégia afterInteractive do next/script:
import Script from "next/script";
// ✅ Carrega apenas após a hidratação do React
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX"
strategy="afterInteractive"
/>
// Para scripts ainda menos críticos (chat widgets, etc.)
<Script
src="https://widget.intercom.io/widget/xxxxx"
strategy="lazyOnload" // carrega no idle do browser
/>
4. Code Splitting com Dynamic Imports
Componentes pesados não precisam estar no bundle inicial. O dynamic() do Next.js cria chunks separados carregados sob demanda:
import dynamic from "next/dynamic";
const HeavyCarousel = dynamic(
() => import("@/components/HeavyCarousel"),
{
loading: () => <div className="h-64 animate-pulse bg-muted rounded-xl" />,
ssr: false, // evita SSR de componentes somente-cliente
}
);
// O componente só é carregado quando entra no viewport
const Chart = dynamic(() => import("recharts").then((m) => m.LineChart), {
ssr: false,
});
5. Preconnect para Domínios Críticos
Adicionei preconnect para domínios usados no carregamento inicial, reduzindo o custo de DNS lookup + handshake TLS:
// app/layout.tsx
export const metadata: Metadata = {
// ...
};
// No componente de layout:
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link rel="dns-prefetch" href="https://www.google-analytics.com" />
</head>
Resultados Finais
Após 3 semanas de otimização progressiva, com deploys e medições a cada etapa:
- Performance: 61 → 98
- Acessibilidade: 72 → 97
- SEO: 68 → 100
- LCP: 4,8s → 1,2s
- CLS: 0,24 → 0,02
- Taxa de rejeição no mês seguinte: -23%
- Conversão: +11%
Performance não é um recurso; é a base para tudo que vem depois. Um segundo a menos no carregamento pode representar +7% de conversão, segundo estudos do Google.
Conclusão
A maioria dos problemas de performance não são complexos de resolver; o difícil é encontrá-los e priorizá-los. Use o DevTools, o PageSpeed Insights e o WebPageTest em conjunto. Meça antes, meça depois. Cada decisão precisa de dados para ser justificada.
Gostou do conteúdo?
Se você precisar de ajuda aplicando essas técnicas no seu projeto, estou disponível para consultoria.

Autor
Paulo Reducino
Desenvolvedor Frontend com 5+ anos de experiência em React, Next.js e TypeScript. Especialista em performance, SEO e acessibilidade. Atualmente na Vizuh (Londres, UK).
