Logo

Cronômetro cromado sobre mesa de vidro escura ao lado de um tablet exibindo interface com gráfico em curva ascendente, em tons de teal e azul-marinho.
Desenvolvimento

Performance front-end na prática: guia para transformar velocidade em receita

Performance front-end na prática, em seis passos, com código que roda em React, Angular, Vue ou JavaScript puro: medir, cortar peso e travar o ganho.

Luiz CoelhoLuiz Coelho8 min de leitura

Todo mundo concorda que site lento perde venda. Poucos times conseguem dizer, com número na mão, onde o tempo se perde, o que consertar primeiro e como impedir que o ganho evapore na próxima entrega. É nesse ponto que performance front-end deixa de ser preferência de desenvolvedor e passa a ser assunto de diretoria. Este post é o roteiro prático que usamos, na ordem em que ele costuma dar mais retorno.

Um aviso útil antes de começar: quase tudo aqui é plataforma web, não framework. Os exemplos rodam igual em React, Angular, Vue ou em uma aplicação sem framework algum, porque quem paga a conta do tempo é o navegador, não a biblioteca escolhida. Onde a sintaxe muda de fato, mostramos o equivalente nos três principais.

Passo 1: medir com dado de campo, não com nota de laboratório

Três métricas resumem a experiência percebida. LCP mede quando o conteúdo principal aparece, e a faixa boa termina em 2,5 segundos. INP mede o atraso entre o clique e a resposta da tela, com alvo em 200 milissegundos. CLS mede o quanto o layout pula durante o carregamento, e o teto é 0,1.

Nota de auditoria rodada no seu notebook, em fibra, diz pouco. O que conta é o dado do usuário real, com a rede e o aparelho que ele tem. Instrumentar isso leva alguns minutos:

import { onLCP, onINP, onCLS, onTTFB } from 'web-vitals';

function enviar({ name, value, rating, id }) {
  const corpo = JSON.stringify({
    metrica: name, valor: value, faixa: rating, id,
    rota: location.pathname,
    conexao: navigator.connection?.effectiveType
  });
  navigator.sendBeacon('/api/vitals', corpo);
}

[onLCP, onINP, onCLS, onTTFB].forEach(registrar => registrar(enviar));

Guarde isso em uma tabela com rota, data e percentil. O número que importa é o p75 por rota, não a média: a média esconde justamente o cliente que desistiu. Complete com o PageSpeed Insights, que já traz o histórico de campo do seu domínio, e com o painel Performance do navegador para investigar caso a caso.

Sem esse passo, todo o resto é chute caro.

Passo 2: abrir o pacote e cortar o que não paga o próprio peso

Na maioria dos projetos, o vilão é JavaScript. Comece olhando o que existe de fato dentro do pacote:

# funciona com qualquer bundler que gere source map
npx source-map-explorer 'dist/**/*.js'           # Vite, esbuild, Rollup
npx source-map-explorer 'build/static/js/*.js'   # Webpack, CRA
npx source-map-explorer 'dist/*/main*.js'        # Angular CLI

Três padrões aparecem quase sempre. O primeiro é a biblioteca importada por inteiro para resolver um detalhe, como formatação de data, quando a plataforma já resolve de graça:

// dezenas de kB no pacote inicial
format(data, 'dd/MM/yyyy');

// nativo, zero kB adicionais
new Intl.DateTimeFormat('pt-BR').format(data);

O segundo é o componente pesado que entra no primeiro carregamento e só é usado por poucos usuários, em telas internas. Editor de texto rico, biblioteca de gráficos, gerador de PDF, mapa. Todos deveriam entrar sob demanda. A ferramenta que resolve isso é do próprio JavaScript, o import() dinâmico, que qualquer bundler entende e transforma em um arquivo separado:

// antes: o editor viaja no pacote inicial de toda visita
import { iniciarEditor } from './rich-editor';

// depois: o arquivo só é baixado nesta linha, quando o formulário abre
const { iniciarEditor } = await import('./rich-editor');

Cada framework embala essa mesma chamada de um jeito, e o efeito na rede é idêntico:

// React
const RichEditor = lazy(() => import('./rich-editor'));

// Vue
const RichEditor = defineAsyncComponent(() => import('./RichEditor.vue'));

// Angular, na definição de rota
{ path: 'editor', loadComponent: () => import('./rich-editor.component')
    .then(m => m.RichEditorComponent) }

O terceiro é a ausência de divisão por rota. Se a página de produto baixa o código do checkout, do painel administrativo e do relatório, o cliente paga por telas que talvez nunca veja. Divisão por rota é ajuste de configuração no bundler, não reescrita.

Em negócio, esse passo é o de melhor relação entre esforço e resultado: quase sempre é onde estão os primeiros segundos recuperados.

Passo 3: consertar a cascata de requisições

Depois do peso vem a ordem. O problema clássico é a cascata: o HTML chega, ele pede o JavaScript, o JavaScript pede a configuração, a configuração pede os dados, e só então a tela desenha. Cada elo adiciona uma ida e volta de rede.

O que resolve, em ordem de impacto: buscar dados no servidor junto com a página, em vez de esperar o navegador pedir depois; disparar chamadas independentes em paralelo com Promise.all, em vez de encadeadas em await sequencial; e avisar o navegador com antecedência sobre o que ele vai precisar.

<link rel="preconnect" href="https://api.seudominio.com.br">
<link rel="preload" as="image" fetchpriority="high" href="/hero.avif">

Uma armadilha que aparece com frequência: a imagem principal da dobra marcada como loading="lazy". Ela é o seu LCP, e o atributo está pedindo ao navegador que a deixe para depois. Lazy loading vale para o que está abaixo da dobra, nunca para o herói.

Passo 4: imagens e fontes, o ganho mais barato do lote

Imagem mal servida é o item que mais pesa em página de conteúdo e de catálogo. O tratamento completo cabe em uma tag:

<img src="/produto-800.avif"
     srcset="/produto-400.avif 400w, /produto-800.avif 800w, /produto-1600.avif 1600w"
     sizes="(max-width: 768px) 100vw, 50vw"
     width="800" height="600"
     alt="Cadeira ergonômica vista de perfil"
     loading="lazy" decoding="async">

Formato moderno reduz o peso, srcset evita entregar 1600 pixels para uma tela de celular, e width com height reserva o espaço antes do download, o que derruba o CLS. Faltando essas duas medidas, o texto pula quando a imagem chega, e o usuário clica no lugar errado.

Fonte é a mesma história com outra roupa. Auto-hospede, gere um subconjunto apenas com os caracteres que o site usa, declare font-display: swap para o texto aparecer imediatamente com a fonte do sistema, e faça preload apenas do peso usado na primeira dobra.

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-latin-subset.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
}

Passo 5: domar os scripts de terceiros

Pixel de anúncio, gerenciador de tags, chat de atendimento, teste A/B, mapa de calor. Cada um entrou por uma decisão legítima, e o conjunto virou o maior consumidor de tempo da página sem que ninguém revisasse a lista.

O procedimento é direto. Monte o inventário, com dono e propósito de cada script, e remova o que não tem dono. Para o que fica, mude a estratégia de carregamento. Chat de atendimento não precisa existir antes do primeiro clique:

const botao = document.querySelector('#abrir-chat');

const carregarChat = () => import('./chat-widget').then(m => m.iniciar());

botao.addEventListener('click', carregarChat, { once: true });

O botão é um desenho estático, barato, e o widget de verdade só desce quando alguém demonstra intenção. A mesma técnica vale para vídeo incorporado, que pode começar como imagem de capa clicável.

Aqui a conversa deixa de ser técnica e passa a ser de governança: alguém precisa ter autoridade para dizer não a mais uma tag. Sem isso, o time otimiza em um trimestre e devolve o ganho no seguinte.

Performance front-end começa na escolha de renderização

Se depois de tudo isso o número ainda não fecha, o problema é arquitetural. Conteúdo público que precisa ser encontrado pede renderização no servidor ou geração estática, com streaming para enviar o que já está pronto em vez de aguardar a página inteira. Aplicação interna autenticada, com sessão longa e muita interação, convive bem com uma abordagem mais próxima do cliente, com hidratação parcial só no que é realmente interativo.

É uma decisão com efeito direto em custo de infraestrutura e em tempo de resposta, e trocá-la depois é caro. Por isso ela pertence ao começo do projeto, não a uma força-tarefa de otimização seis meses após o lançamento.

Passo 6: travar o ganho com orçamento de performance no pipeline

Ganho de performance não se sustenta sozinho. O que segura é transformar velocidade em limite verificável, do mesmo jeito que um teste quebrado impede o merge:

{
  "ci": {
    "collect": {
      "url": ["https://staging.seudominio.com.br/", "https://staging.seudominio.com.br/checkout"],
      "numberOfRuns": 3
    },
    "assert": {
      "assertions": {
        "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
        "total-blocking-time": ["error", { "maxNumericValue": 200 }],
        "total-byte-weight": ["warn", { "maxNumericValue": 1000000 }]
      }
    }
  }
}

No laboratório não existe INP, então use o total blocking time como aproximação e confirme o INP real no dado de campo do passo 1. Some a isso um teto de peso para o pacote inicial verificado no build, e um alerta quando o p75 de produção piorar mais de dez por cento entre versões. Versionamento e rollback fecham o ciclo: quando uma dependência nova degrada a experiência, você volta em minutos, sem discussão.

Um plano de trinta dias que costuma funcionar

Na Alcance Tech, quando entramos em um projeto com esse sintoma, a sequência é sempre a mesma. Na primeira semana, instrumentar o dado de campo e escolher as cinco rotas que carregam receita, tipicamente entrada de campanha, busca, produto, cadastro e checkout, com uma linha de base registrada. Na segunda, pacote e terceiros, que é onde o ganho aparece mais rápido. Na terceira, imagem, fonte, cascata e política de cache. Na quarta, orçamento no pipeline, alerta de regressão e, se o alvo ainda não fechou, a conversa de renderização.

O resultado desse recorte é que o time trabalha no que aparece no funil, e não em uma lista infinita de melhorias tecnicamente elegantes que nenhum cliente percebe.

Velocidade de interface não é capricho estético. É a primeira promessa que o seu produto faz ao cliente, e a mais fácil de quebrar sem que ninguém no time perceba.


Precisa de ajuda com tecnologia?

Conheça as soluções da Alcance Tech e leve seu negócio ao próximo nível.

Terno sob medida com lapela de circuito — desenvolvimento de software sob medida da Alcance Tech

Desenvolvimento

Desenvolvimento de software

Saiba mais →
Ímã atraindo leads sobre gráficos em alta — marketing digital orientado a resultado da Alcance Tech

Marketing

Marketing digital

Saiba mais →
Engrenagens de precisão ao lado de um dashboard — automação de processos da Alcance Tech

Processos

Automação de processos

Saiba mais →
Prisma refratando luz em espectro sobre gráficos — inteligência artificial e dados da Alcance Tech

Dados e I.A.

Inteligência artificial e dados

Saiba mais →