Alternar tema

Otimização do Core Web Vitals no Next.js: guia completo de LCP, FCP e CLS

Easton editorial illustration: state-management shelf

No mês passado, assumi a otimização de desempenho de um projeto de e-commerce. A pontuação do Lighthouse ficava em torno de 65, e o LCP (Largest Contentful Paint) oscilava perto de 3,5 segundos. O responsável pelo negócio dizia que a taxa de abandono era alta e que a conversão não melhorava.

Depois de duas semanas otimizando sistematicamente o Core Web Vitals, levei a pontuação do Lighthouse a 95 e reduzi o LCP para 1,2 segundo. Mais importante: a taxa de conversão aumentou 28%. Otimização de desempenho não é apenas uma questão de indicadores técnicos; ela gera valor real para o negócio.

Segundo os dados mais recentes de 2025, apenas 47% dos sites atendem aos padrões de Core Web Vitals do Google. Um desempenho ruim pode causar perdas de 8% a 35% em receita, ranqueamento e conversão. Não é alarmismo, mas um custo comercial concreto.

Neste artigo, compartilho o que aprendi na prática ao otimizar o desempenho de projetos Next.js. Você vai aprender:

  • Como otimizar os três indicadores principais: LCP, INP e CLS
  • Mais de 10 exemplos de código que você pode copiar diretamente
  • As cinco armadilhas de desempenho mais comuns
95 pontos
Lighthouse após a otimização
Antes eram 65
1,2 s
LCP após a otimização
Antes eram 3,5 s
28%
Aumento na conversão
Valor comercial da otimização
47%
Sites dentro do padrão
Dados de Core Web Vitals de 2025
Source: Dados práticos

O que mudou no Core Web Vitals em 2025

Antes de começar a otimizar, precisamos entender as regras atuais. Muitos artigos ainda falam de FID (First Input Delay), mas esse indicador foi descontinuado em março de 2024.

Os três indicadores principais atuais

Hoje, o Google prioriza estes três indicadores:

  1. LCP (Largest Contentful Paint) — maior renderização de conteúdo

    • Meta: ≤ 2,5 segundos
    • Mede o desempenho de carregamento
    • Representa 25% da pontuação do Lighthouse
  2. INP (Interaction to Next Paint) — interação até a próxima renderização

    • Meta: ≤ 200 ms
    • Mede a capacidade de resposta às interações
    • Substituiu o FID em março de 2024
  3. CLS (Cumulative Layout Shift) — mudança cumulativa de layout

    • Meta: < 0,1
    • Mede a estabilidade visual

O FCP (First Contentful Paint) deixou de ser um indicador principal, mas ainda é importante porque afeta a primeira impressão do usuário.

Por que acompanhar esses indicadores?

Talvez você já tenha aberto um site e, no momento de clicar em um botão, a página tenha se deslocado de repente, fazendo você clicar no lugar errado. Isso é um exemplo de CLS ruim.

Ou talvez você tenha acessado um site e ficado vários segundos olhando para uma tela em branco, pensando se a conexão havia caído. Esse é o efeito de um LCP lento.

Segundo dados da equipe do Chrome, os melhores sites têm LCP médio de cerca de 1.220 ms. Se o LCP do seu site ultrapassa 2,5 segundos, você já está atrás de grande parte dos concorrentes.

Otimização de LCP: exiba rapidamente o maior elemento de conteúdo

O LCP foi o indicador em que investi mais tempo e também aquele que mostrou o resultado mais evidente. A seguir, vamos otimizar esse ponto passo a passo.

Primeiro passo: identifique o elemento de LCP

Antes de otimizar, você precisa saber qual elemento está definindo o LCP. Em geral, ele é:

  • A imagem principal da primeira tela
  • A capa de um vídeo
  • Um título grande ou um bloco extenso de texto

Como verificar rapidamente:

  1. Abra o Chrome DevTools com F12
  2. Pressione Ctrl+Shift+P — no Mac, use Cmd+Shift+P
  3. Digite “Show Rendering”
  4. Marque “Core Web Vitals”
  5. Atualize a página; o elemento de LCP será mostrado no canto superior direito

No projeto de e-commerce, descobri que o elemento de LCP era a imagem principal da página inicial: um JPG de 2 MB. Ali estava o problema.

Otimização de imagens: como usar next/image corretamente

Muita gente pensa que basta usar o componente Image do Next.js para resolver tudo. Eu também pensava assim no começo, mas o LCP continuava lento.

Definir a prioridade é o ponto decisivo

Para uma imagem de LCP, você precisa dizer explicitamente ao navegador que ela é importante e deve ser carregada primeiro. O Next.js oferece duas formas de fazer isso.

Next.js 13 a 15, as versões mais usadas:

import Image from 'next/image';

export default function Hero() {
  return (
    <Image
      src="/hero-image.jpg"
      width={1200}
      height={630}
      priority  // Essencial: instrui o navegador a priorizar a imagem
      fetchPriority="high"  // Uma proteção adicional
      alt="Imagem principal do produto"
    />
  );
}

Next.js 16 ou superior, a versão mais recente:

No Next.js 16, a propriedade priority foi descontinuada. Use esta forma:

<Image
  src="/hero-image.jpg"
  width={1200}
  height={630}
  loading="eager"  // Carrega imediatamente, sem lazy loading
  fetchPriority="high"  // Prioridade alta
  alt="Imagem principal do produto"
/>

Você pode se perguntar por que definir duas propriedades. priority adiciona automaticamente uma tag de pré-carregamento, enquanto fetchPriority informa ao navegador a importância do recurso. As duas funcionam melhor em conjunto.

Compare com estas formas incorretas:

// ❌ Incorreto: a imagem será carregada sob demanda, deixando o LCP lento
<Image
  src="/hero-image.jpg"
  width={1200}
  height={630}
  alt="Imagem principal do produto"
/>

// ❌ Incorreto: faltam informações de dimensão
<Image
  src="/hero-image.jpg"
  priority
  alt="Imagem principal do produto"
/>

A importância de declarar as dimensões

O componente Image do Next.js exige width e height. Não é uma dificuldade arbitrária: essas propriedades evitam CLS, ou seja, deslocamentos no layout.

Em um layout responsivo, você pode escrever assim:

// Use a propriedade fill para criar uma imagem responsiva
<div style={{ position: 'relative', width: '100%', aspectRatio: '16/9' }}>
  <Image
    src="/hero-image.jpg"
    fill
    priority
    style={{ objectFit: 'cover' }}
    alt="Imagem principal do produto"
  />
</div>

O contêiner pai precisa ter dimensões explícitas ou um aspect-ratio. Caso contrário, a imagem não sabe qual tamanho deve ocupar.

Otimização de fontes: incorporação automática com next/font

Fontes lentas também prejudicam o LCP, especialmente fontes com conjuntos de caracteres grandes, que podem ocupar vários megabytes. O Next.js 13 introduziu o next/font, que otimiza automaticamente o carregamento.

Usando Google Fonts:

// app/layout.jsx
import { Inter, Noto_Sans_SC } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',  // Evita o desaparecimento temporário do texto
});

const notoSansSC = Noto_Sans_SC({
  subsets: ['chinese-simplified'],
  weight: ['400', '700'],
  display: 'swap',
});

export default function RootLayout({ children }) {
  return (
    <html lang="zh-CN" className={`${inter.className} ${notoSansSC.className}`}>
      <body>{children}</body>
    </html>
  );
}

Usando uma fonte personalizada:

import localFont from 'next/font/local';

const myFont = localFont({
  src: './my-font.woff2',
  display: 'swap',
});

export default function Layout({ children }) {
  return <div className={myFont.className}>{children}</div>;
}

O next/font aplica automaticamente estas otimizações:

  • Incorpora o CSS das fontes e reduz o número de requisições
  • Cria subconjuntos e carrega apenas os caracteres necessários
  • Pré-carrega os arquivos de fonte
  • Elimina deslocamentos de layout

Depois que migrei as fontes do projeto para next/font, o LCP caiu mais 0,3 segundo.

Reduza o tempo de resposta do servidor

Você já otimizou imagens e fontes, mas o LCP continua lento? O problema pode estar no tempo de resposta do servidor, ou TTFB.

Priorize a geração estática

O Next.js oferece várias formas de renderização. Da mais rápida para a mais lenta, a ordem é:

  1. SSG (Static Site Generation) — gera o HTML no build e é a opção mais rápida
  2. ISR (Incremental Static Regeneration) — regenera páginas estáticas sob demanda
  3. SSR (Server-Side Rendering) — gera o HTML a cada requisição e é mais lento
  4. CSR (Client-Side Rendering) — renderiza no cliente e costuma ter o LCP mais lento

Sempre que puder, use SSG:

// app/blog/[slug]/page.jsx
export async function generateStaticParams() {
  const posts = await getPosts();
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

export default async function BlogPost({ params }) {
  const post = await getPost(params.slug);
  return <article>{/* ... */}</article>;
}

Se os dados precisam de atualização frequente, use ISR:

// app/products/[id]/page.jsx
export const revalidate = 3600; // Regenera uma vez por hora

export default async function ProductPage({ params }) {
  const product = await getProduct(params.id);
  return <div>{/* ... */}</div>;
}

Use uma CDN e uma Edge Network

Se você implanta o projeto na Vercel, os recursos estáticos são distribuídos automaticamente pela Edge Network global, reduzindo bastante o TTFB.

Em outras plataformas, você pode usar uma CDN como Cloudflare ou AWS CloudFront.

Evite cálculos complexos no servidor

Eu também já caí nessa armadilha. Em um projeto, fazia um processamento complexo no servidor que levava 500 ms a cada renderização e prejudicava diretamente o LCP.

Exemplo do que não fazer:

// ❌ Incorreto: calcula tudo em cada requisição
export default async function Page() {
  const data = await fetchData();
  const processed = heavyProcessing(data); // Leva 500 ms
  return <div>{processed}</div>;
}

Forma correta:

Armazene o resultado em cache ou faça o cálculo durante o build:

// ✅ Correto: calcula durante o build
export async function generateStaticParams() {
  const data = await fetchData();
  const processed = heavyProcessing(data);
  // Armazene o resultado no banco de dados ou em um arquivo
  await saveProcessedData(processed);
}

export default async function Page() {
  const processed = await getProcessedData(); // Apenas lê o resultado
  return <div>{processed}</div>;
}

Otimização de CLS: elimine os saltos no layout

O CLS (Cumulative Layout Shift) é um dos indicadores mais incômodos. Quando a página muda de posição de repente, a experiência do usuário fica ruim. Para ser sincero, esse problema me incomodou por bastante tempo.

Reserve espaço para imagens e elementos de mídia

A causa mais comum de CLS é uma imagem que altera o layout depois de carregar. A solução é simples: informe antecipadamente as dimensões ao navegador.

O componente Image do Next.js cuida disso automaticamente:

// ✅ Correto: as dimensões evitam CLS
<Image
  src="/product.jpg"
  width={400}
  height={300}
  alt="Imagem do produto"
/>

E quando a imagem é carregada dinamicamente?

Imagine que você receba uma imagem de um CMS sem saber suas dimensões:

// ✅ Correto: use aspect-ratio para reservar espaço
<div style={{ position: 'relative', width: '100%', aspectRatio: '4/3' }}>
  <Image
    src={dynamicImageUrl}
    fill
    style={{ objectFit: 'cover' }}
    alt="Imagem dinâmica"
  />
</div>

O mesmo vale para vídeos:

<video
  width="1280"
  height="720"
  poster="/video-poster.jpg"
  controls
>
  <source src="/video.mp4" type="video/mp4" />
</video>

Reserve espaço para conteúdo dinâmico

Anúncios, barras de aviso e conteúdo incorporado, como cards do Twitter, podem causar CLS. Reserve espaço para eles com antecedência.

Contêiner de anúncio:

// ✅ Correto: reserva a altura do anúncio
<div
  style={{
    minHeight: '250px',  // Altura padrão de um anúncio do Google AdSense
    backgroundColor: '#f0f0f0'  // Cor de fundo do espaço reservado
  }}
>
  {/* Script do anúncio */}
  <ins className="adsbygoogle" />
</div>

Barra de aviso:

Se o site tem uma barra de aviso no topo, não deixe que ela apareça de repente:

// ❌ Incorreto: aparece de repente e empurra a página para baixo
{showBanner && <NotificationBanner />}

// ✅ Correto: reserva o espaço antecipadamente
<div style={{ minHeight: '60px' }}>
  {showBanner ? <NotificationBanner /> : <div style={{ height: '60px' }} />}
</div>

Skeleton screen:

Para conteúdo carregado dinamicamente, use um skeleton como espaço reservado:

function ProductList() {
  const { data, isLoading } = useQuery('products', fetchProducts);

  if (isLoading) {
    return (
      <div className="grid grid-cols-3 gap-4">
        {Array.from({ length: 6 }).map((_, i) => (
          <div key={i} className="skeleton" style={{ height: '300px' }} />
        ))}
      </div>
    );
  }

  return (
    <div className="grid grid-cols-3 gap-4">
      {data.map(product => <ProductCard key={product.id} {...product} />)}
    </div>
  );
}

Otimize o carregamento de fontes

O carregamento de fontes também pode causar CLS, normalmente percebido como texto piscando ou mudando de tamanho.

O next/font trata esse problema automaticamente, mas você também pode ajustar o comportamento:

const inter = Inter({
  subsets: ['latin'],
  display: 'optional',  // Se a fonte não carregar a tempo, usa a fonte do sistema
  adjustFontFallback: true,  // Ajusta a fonte alternativa para reduzir deslocamentos
});

Opções da propriedade display:

  • swap: mostra primeiro a fonte alternativa e troca quando a fonte termina de carregar; pode causar CLS
  • optional: se a fonte não carregar rapidamente, mantém a fonte alternativa; é a opção recomendada
  • block: oculta o texto por alguns instantes até a fonte carregar; não é recomendado
  • fallback: fica entre swap e optional

Recomendo optional. Mesmo que a fonte personalizada nem sempre apareça, a experiência do usuário será melhor.

Evite layouts responsivos controlados por JavaScript

Esta é uma das armadilhas de CLS mais comuns em 2025. Vejo muitos projetos Next.js cometendo esse erro.

Código incorreto típico:

// ❌ Incorreto: causa um CLS considerável
import { useMediaQuery } from '@/hooks/useMediaQuery';

export default function ResponsiveLayout() {
  const isMobile = useMediaQuery('(max-width: 768px)');

  return (
    <div>
      {isMobile ? (
        <MobileNav />
      ) : (
        <DesktopNav />
      )}
    </div>
  );
}

Por que isso causa CLS?

  1. Na primeira renderização, o JavaScript ainda não foi executado e isMobile é undefined ou usa um valor padrão
  2. Depois que o JavaScript roda, useMediaQuery retorna o valor real
  3. O componente renderiza novamente e o layout muda bruscamente

O problema fica ainda pior com SSR, porque o servidor não conhece a largura da tela do cliente.

A forma correta: use media queries em CSS

// ✅ Correto: o CSS controla a visibilidade sem causar CLS
export default function ResponsiveLayout() {
  return (
    <>
      <nav className="mobile-nav md:hidden">
        <MobileNav />
      </nav>
      <nav className="desktop-nav hidden md:block">
        <DesktopNav />
      </nav>
    </>
  );
}

Ou use CSS-in-JS:

export default function ResponsiveLayout() {
  return (
    <div className="responsive-container">
      <MobileNav />
      <DesktopNav />
      <style jsx>{`
        .responsive-container > :global(.mobile-nav) {
          display: block;
        }
        .responsive-container > :global(.desktop-nav) {
          display: none;
        }
        @media (min-width: 768px) {
          .responsive-container > :global(.mobile-nav) {
            display: none;
          }
          .responsive-container > :global(.desktop-nav) {
            display: block;
          }
        }
      `}</style>
    </div>
  );
}

Se você realmente precisa tomar a decisão com JavaScript, por exemplo para fazer requisições de dados diferentes conforme o dispositivo, obtenha o User-Agent no servidor:

// app/page.jsx
import { headers } from 'next/headers';

export default async function Page() {
  const headersList = headers();
  const userAgent = headersList.get('user-agent') || '';
  const isMobile = /mobile/i.test(userAgent);

  return (
    <div>
      {isMobile ? <MobileView /> : <DesktopView />}
    </div>
  );
}

Assim, o servidor e o cliente produzem o mesmo resultado, sem causar CLS.

Otimização de FCP: acelere a primeira renderização de conteúdo

Embora o FCP (First Contentful Paint) não seja mais um indicador principal, ele afeta a primeira impressão. Se o usuário vê uma tela em branco por muito tempo, pode fechar a página imediatamente.

Otimize os recursos essenciais

Um FCP lento normalmente é causado por recursos que bloqueiam a renderização. Abra o painel Coverage do Chrome DevTools e veja quais arquivos CSS e JavaScript não estão sendo usados.

Incorpore o CSS essencial:

O Next.js otimiza CSS automaticamente, mas você também pode incorporar manualmente os estilos essenciais:

// app/layout.jsx
export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        <style
          dangerouslySetInnerHTML={{
            __html: `
              /* CSS essencial para a primeira tela */
              body { margin: 0; font-family: system-ui; }
              .hero { height: 100vh; }
            `,
          }}
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

Adie o CSS não essencial:

Estilos que não são necessários na primeira tela, como os de modais ou áreas recolhidas, podem ser carregados depois:

// Importe CSS dinamicamente no componente
import dynamic from 'next/dynamic';

const Modal = dynamic(() => import('./Modal'), {
  loading: () => <p>Carregando...</p>,
});

Gerencie scripts de terceiros

Scripts de terceiros, como Google Analytics, anúncios e plugins de redes sociais, prejudicam muito o desempenho. Em muitos sites, eles são a causa do FCP lento.

Use next/script:

O Next.js oferece o componente Script, que permite controlar quando um script será carregado:

import Script from 'next/script';

export default function Layout({ children }) {
  return (
    <>
      {children}

      {/* Google Analytics: carrega depois que a página fica interativa */}
      <Script
        src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
        strategy="lazyOnload"
      />
      <Script id="ga-init" strategy="lazyOnload">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', 'GA_MEASUREMENT_ID');
        `}
      </Script>

      {/* Facebook Pixel: carregamento adiado */}
      <Script
        src="https://connect.facebook.net/en_US/fbevents.js"
        strategy="lazyOnload"
      />
    </>
  );
}

Como funciona a propriedade strategy:

  • beforeInteractive: carrega antes que a página fique interativa; use para scripts essenciais
  • afterInteractive: carrega depois que a página fica interativa; é o padrão
  • lazyOnload: carrega durante o tempo ocioso; recomendado para análise e anúncios
  • worker: executa em um Web Worker; é um recurso experimental

Depois que mudei todos os scripts de terceiros não essenciais para lazyOnload, o FCP melhorou 0,8 segundo.

Divisão de código e carregamento sob demanda

O Next.js faz divisão de código automaticamente, mas em alguns casos você precisa otimizar manualmente.

Carregamento sob demanda de componentes:

Para componentes que não aparecem na primeira tela, use importação dinâmica:

import dynamic from 'next/dynamic';

// Carrega o componente de comentários sob demanda
const Comments = dynamic(() => import('./Comments'), {
  loading: () => <div>Carregando comentários...</div>,
  ssr: false,  // Não usa renderização no servidor
});

export default function BlogPost({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
      {/* Os comentários só são carregados quando o usuário chega aqui */}
      <Comments postId={post.id} />
    </article>
  );
}

Carregamento sob demanda de bibliotecas de terceiros:

Algumas bibliotecas, como ferramentas de gráficos e editores de texto avançados, são grandes e devem ser carregadas apenas quando necessário.

Caso real: adiar uma biblioteca de gráficos economizou 800 KB

Em um projeto anterior, todas as páginas importavam o Chart.js, embora apenas o dashboard o utilizasse. Como resultado, cada página carregava 800 KB de código desnecessário.

Antes da otimização:

// ❌ Incorreto: a importação global carrega o código em todas as páginas
import { Chart } from 'chart.js';

export default function Dashboard() {
  return <canvas ref={chartRef} />;
}

Depois da otimização:

// ✅ Correto: carrega apenas quando necessário
import dynamic from 'next/dynamic';

const ChartComponent = dynamic(() => import('./ChartComponent'), {
  loading: () => <div>Carregando gráfico...</div>,
  ssr: false,
});

export default function Dashboard() {
  return <ChartComponent />;
}

// ChartComponent.jsx
import { Chart } from 'chart.js';

export default function ChartComponent() {
  return <canvas ref={chartRef} />;
}

Assim, apenas quem acessa o dashboard baixa o Chart.js; as demais páginas não são afetadas.

Carregamento sob demanda de imagens:

Com exceção da imagem de LCP, as outras imagens devem usar lazy loading:

<Image
  src="/feature-image.jpg"
  width={600}
  height={400}
  loading="lazy"  // É o valor padrão e pode ser omitido
  alt="Apresentação do recurso"
/>

Monitoramento e otimização contínua

Concluir uma rodada de otimização não significa que o trabalho acabou. Desempenho é um processo contínuo, que exige verificações e ajustes regulares.

Automatize os testes com Lighthouse CI

Executar o Lighthouse manualmente consome tempo e é fácil esquecer. Uma opção melhor é integrá-lo ao processo de CI/CD.

Instale o Lighthouse CI:

npm install -D @lhci/cli

Arquivo de configuração lighthouserc.js:

module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000/', 'http://localhost:3000/products'],
      numberOfRuns: 3,  // Executa três vezes e usa a média
    },
    assert: {
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],  // Performance ≥ 90
        'first-contentful-paint': ['error', { maxNumericValue: 2000 }],  // FCP ≤ 2 s
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],  // LCP ≤ 2,5 s
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],  // CLS < 0,1
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};

Configuração do GitHub Actions:

# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]
jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm install
      - run: npm run build
      - run: npm run start &
      - run: npx @lhci/cli autorun

Assim, cada push executa automaticamente os testes do Lighthouse. Se o desempenho regredir, o CI falha e avisa que você precisa corrigir o problema.

Real User Monitoring (RUM)

O Lighthouse testa em um ambiente de laboratório, mas a experiência dos usuários reais pode ser diferente. Por isso, você precisa coletar dados reais de Core Web Vitals.

Usando Vercel Analytics:

Se o projeto está implantado na Vercel, você pode ativar o Analytics com poucos passos:

// app/layout.jsx
import { Analytics } from '@vercel/analytics/react';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Analytics />
      </body>
    </html>
  );
}

Usando a biblioteca web-vitals:

Se você não usa a Vercel, pode recorrer à biblioteca web-vitals do Google:

npm install web-vitals
// app/layout.jsx
'use client';

import { useEffect } from 'react';
import { onLCP, onFID, onCLS, onINP } from 'web-vitals';

function sendToAnalytics(metric) {
  // Envia os dados ao seu serviço de análise
  fetch('/api/analytics', {
    method: 'POST',
    body: JSON.stringify(metric),
  });
}

export default function Analytics() {
  useEffect(() => {
    onLCP(sendToAnalytics);
    onINP(sendToAnalytics);
    onCLS(sendToAnalytics);
  }, []);

  return null;
}

Google Search Console:

O relatório de “Core Web Vitals” do Google Search Console mostra o desempenho do seu site para usuários reais e indica quais páginas precisam melhorar.

Armadilhas comuns e como resolvê-las

Para encerrar, reuni as cinco armadilhas mais frequentes.

Armadilha 1: usar JavaScript demais no cliente

Muitos desenvolvedores tentam fazer tudo com React, e a página inteira acaba dependendo de JavaScript. Se o JS falha ou demora para carregar, o usuário vê uma tela em branco.

Solução:

  • Priorize Server Components, que são o padrão a partir do Next.js 13
  • Use 'use client' apenas quando for necessário
  • Aplique Progressive Enhancement: garanta primeiro a funcionalidade básica e depois melhore a interação

Armadilha 2: ignorar o desempenho em dispositivos móveis

Um site rápido no desktop não é necessariamente rápido no celular. Dispositivos móveis têm CPU mais fraca e redes mais lentas, o que torna os problemas de desempenho mais evidentes.

Solução:

  • Teste no modo “Mobile” do Lighthouse
  • Limite a velocidade da rede em Chrome DevTools → Network → Throttling
  • Limite a CPU em Chrome DevTools → Performance → CPU

Armadilha 3: não otimizar scripts de terceiros

Google Analytics, Facebook Pixel, Intercom, Hotjar… Cada script prejudica o desempenho.

Solução:

  • Envolva todos com next/script e defina strategy="lazyOnload"
  • Revise periodicamente quais scripts são realmente necessários e quais podem ser removidos
  • Considere substituir o rastreamento no cliente por rastreamento no servidor

Armadilha 4: escolher formatos de imagem inadequados

Ainda usa PNG e JPG? WebP e AVIF podem reduzir o tamanho dos arquivos em 30% a 50%.

Solução:

O componente Image do Next.js converte os formatos automaticamente, mas o servidor precisa oferecer suporte:

// next.config.js
module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],  // Prioriza formatos modernos
  },
};

Armadilha 5: ignorar o desempenho do servidor

Mesmo que o frontend esteja perfeitamente otimizado, um servidor lento prejudica todo o resultado.

Solução:

  • Otimize consultas ao banco de dados com índices e redução de consultas N+1
  • Use cache com Redis ou CDN
  • Monitore o desempenho do servidor com ferramentas de APM, como New Relic ou Datadog

Resumo

Vamos recapitular os pontos principais.

Para otimizar o LCP:

  • Defina priority e fetchPriority="high" na imagem de LCP
  • Use next/font para otimizar o carregamento de fontes
  • Priorize SSG e ISR e reduza cálculos no servidor
  • Use uma CDN para diminuir o TTFB

Para otimizar o CLS:

  • Defina as dimensões de todas as imagens e elementos de mídia
  • Reserve espaço para conteúdo dinâmico, como anúncios e barras de aviso
  • Evite hooks JavaScript para criar layouts responsivos
  • Use next/font para reduzir mudanças durante o carregamento de fontes

Para otimizar o FCP:

  • Adie recursos não essenciais
  • Envolva scripts de terceiros com next/script e use lazyOnload
  • Carregue bibliotecas grandes e componentes fora da primeira tela sob demanda

Para manter os resultados:

  • Automatize os testes com Lighthouse CI
  • Colete dados de usuários reais com RUM
  • Revise e remova regularmente o código desnecessário

Otimização de desempenho é um ciclo de “medir → otimizar → medir novamente”. Não espere resolver tudo de uma vez; acompanhe e melhore continuamente.

Quando vi a pontuação do Lighthouse finalmente ultrapassar 90, a sensação de realização foi enorme. Mais importante, o aumento na taxa de conversão provou que o esforço valeu a pena.

Checklist de ações

Agora é a sua vez. Recomendo começar nesta ordem:

  1. Faça agora:

    • Teste seu projeto Next.js com o Lighthouse
    • Identifique o elemento de LCP e verifique se ele usa priority
    • Procure por códigos como useMediaQuery que podem causar CLS
  2. Conclua nesta semana:

    • Otimize a imagem de LCP com priority e fetchPriority
    • Migre as fontes para next/font
    • Adicione espaços reservados para conteúdo dinâmico
  3. Conclua na próxima semana:

    • Migre scripts de terceiros para lazyOnload
    • Carregue bibliotecas grandes e componentes fora da primeira tela sob demanda
    • Configure testes automatizados com Lighthouse CI
  4. Faça continuamente:

    • Verifique a pontuação do Lighthouse todos os meses
    • Acompanhe o relatório de Core Web Vitals no Google Search Console
    • Corrija regressões de desempenho rapidamente

Lembre-se: otimização de desempenho não é uma tarefa pontual, mas um processo contínuo. Boa otimização!

Processo completo de otimização do Core Web Vitals no Next.js

Otimize LCP, INP e CLS e aumente a pontuação do Lighthouse para mais de 90.

⏱️ Estimated time: 8 hr

  1. 1

    Step 1: Otimizar o LCP (Largest Contentful Paint)

    Meta: ≤ 2,5 s

    Como otimizar:
    • Use next/image para otimizar imagens, com conversão automática para WebP/AVIF
    • Adicione priority às imagens essenciais da primeira tela
    • Pré-carregue recursos essenciais com <link rel="preload">
    • Melhore o tempo de resposta do servidor com CDN e Edge Functions
    • Reduza recursos que bloqueiam a renderização, adiando CSS/JS não essencial
    • Use React Server Components para reduzir o JavaScript no cliente

    Ferramentas de verificação:
    • Relatório Performance do Lighthouse
    • Painel Performance do Chrome DevTools
    • Teste on-line do WebPageTest
  2. 2

    Step 2: Otimizar o INP (Interaction to Next Paint)

    Meta: ≤ 200 ms

    Como otimizar:
    • Reduza o tempo de execução do JavaScript com divisão de código e carregamento sob demanda
    • Use React Server Components para reduzir o JavaScript no cliente
    • Otimize eventos com debounce, throttle e delegação de eventos
    • Evite tarefas longas que bloqueiam a thread principal usando Web Workers
    • Otimize scripts de terceiros com carregamento adiado e async/defer
    • Use Suspense e renderização por streaming

    Ferramentas de verificação:
    • Painel Performance do Chrome DevTools
    • Extensão Web Vitals para Chrome
    • Ferramentas de Real User Monitoring (RUM)
  3. 3

    Step 3: Otimizar o CLS (Cumulative Layout Shift)

    Meta: ≤ 0,1

    Como otimizar:
    • Defina largura e altura de imagens e vídeos ou use aspect-ratio
    • Evite inserir conteúdo dinamicamente e reserve espaço antecipadamente
    • Use font-display: swap para evitar deslocamentos durante o carregamento de fontes
    • Reserve espaço para anúncios para que eles não empurrem o conteúdo ao carregar
    • Use CSS Grid/Flexbox em vez de posicionamento absoluto
    • Evite inserir conteúdo novo acima do conteúdo existente

    Ferramentas de verificação:
    • Relatório de CLS do Lighthouse
    • Registros de Layout Shift no painel Performance do Chrome DevTools
    • Visualização de CLS no WebPageTest
  4. 4

    Step 4: Usar os recursos de desempenho do Next.js

    Técnicas de otimização no Next.js:
    • Use o componente Image para otimizar imagens automaticamente
    • Use Font Optimization para otimizar fontes automaticamente
    • Use React Server Components para reduzir o JavaScript no cliente
    • Use Streaming SSR para acelerar a primeira tela
    • Use Dynamic Imports para dividir o código
    • Use next/dynamic para adiar o carregamento de componentes

    Verificação de configuração:
    • Configurações de desempenho em next.config.js
    • Verifique o tamanho do bundle com npm run build
  5. 5

    Step 5: Monitorar e otimizar continuamente

    Ferramentas de monitoramento:
    • Relatório de Core Web Vitals do Google Search Console
    • Vercel Analytics, se você usa a Vercel
    • Extensão Web Vitals para Chrome
    • Ferramentas de Real User Monitoring (RUM)

    Otimização contínua:
    • Verifique a pontuação do Lighthouse todos os meses
    • Acompanhe o relatório de Core Web Vitals no Search Console
    • Corrija regressões de desempenho rapidamente
    • Faça testes A/B para avaliar as otimizações

FAQ

Quais são os três indicadores do Core Web Vitals?
LCP (Largest Contentful Paint) ≤ 2,5 s mede o desempenho de carregamento; INP (Interaction to Next Paint) ≤ 200 ms mede a capacidade de resposta e substituiu o FID em 2024; CLS (Cumulative Layout Shift) ≤ 0,1 mede a estabilidade do layout. Esses três indicadores afetam diretamente o ranqueamento no Google.
Como otimizar o LCP?
Use next/image para otimizar imagens, adicione priority às imagens essenciais da primeira tela, pré-carregue recursos importantes, melhore o tempo de resposta do servidor, reduza recursos que bloqueiam a renderização e use React Server Components. A meta é manter o LCP em até 2,5 segundos.
Qual é a diferença entre INP e FID?
O INP substituiu o FID em março de 2024. O FID media apenas a primeira interação, enquanto o INP avalia o tempo de resposta de todas as interações. Por isso, o INP representa melhor a experiência real do usuário. A meta é ≤ 200 ms.
Como evitar CLS, ou deslocamentos de layout?
Defina largura e altura de imagens e vídeos, evite inserir conteúdo dinamicamente, use font-display: swap, reserve espaço para anúncios, use CSS Grid/Flexbox e não insira conteúdo novo acima do conteúdo existente. A meta é manter o CLS em até 0,1.
Quanto tempo leva para perceber os resultados da otimização?
Os indicadores técnicos, como a pontuação do Lighthouse, mostram resultados imediatamente. Já métricas de negócio, como taxa de conversão e ranqueamento, normalmente levam de um a três meses. O Google precisa reavaliar o site e os dados de comportamento dos usuários precisam se acumular. Por isso, monitore e otimize continuamente.
Quais recursos do Next.js ajudam a melhorar o desempenho?
React Server Components reduzem o JavaScript no cliente; o componente Image otimiza imagens automaticamente; Font Optimization melhora o carregamento de fontes; Streaming SSR acelera a primeira tela; Dynamic Imports fazem divisão de código; e o framework também oferece code splitting e tree-shaking automáticos. Usar bem esses recursos pode melhorar muito o desempenho.
Como monitorar o Core Web Vitals?
Use o relatório de Core Web Vitals do Google Search Console para dados de usuários reais, o Lighthouse para dados de laboratório, a extensão Web Vitals para Chrome, o Vercel Analytics se você usa a Vercel e ferramentas de Real User Monitoring. O ideal é combinar dados de laboratório e de usuários reais.

19 min de leitura · Publicado em: 19 dez 2025 · Atualizado em: 4 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog