Otimização de performance de páginas web (LPs, páginas de vendas, obrigado)...
Transforma qualquer LP ou página web em uma página com score 90+ no PageSpeed Insights (mobile). Baseada em técnicas validadas empiricamente em dezenas de landing pages reais, com múltiplas iterações e trade-offs documentados.
Resultado esperado: Score 90+ mobile, LCP < 2.5s, CLS < 0.1, FCP < 1.8s.
Seguir NESTA ORDEM — cada etapa está ranqueada pelo impacto real medido.
Antes de qualquer mudança, medir o estado atual.
PageSpeed Insights (web):
https://pagespeed.web.dev/analysis?url=https://SEU-DOMINIO.com.br
Lighthouse CLI (local, mais controle):
npx lighthouse https://SEU-DOMINIO.com.br --output=json --output-path=./report.json --preset=perf --chrome-flags="--headless"
Métricas a registrar:
| Métrica | Meta | O que mede |
|---|---|---|
| Performance Score | 90+ | Score geral |
| LCP (Largest Contentful Paint) | < 2.5s | Quando o maior elemento renderiza |
| CLS (Cumulative Layout Shift) | < 0.1 | Quanto a página "pula" |
| FCP (First Contentful Paint) | < 1.8s | Primeiro conteúdo visível |
| Speed Index | < 3.4s | Velocidade de preenchimento visual |
| TBT (Total Blocking Time) | < 200ms | Tempo que JS bloqueia interação |
Anotar: Score atual, maiores oportunidades listadas pelo PageSpeed, tamanho total da página.
Imagens são quase sempre o maior gargalo. Uma foto JPG de 500KB pode virar 45KB WebP.
# Requer: pip install Pillow
from PIL import Image
import os
def optimize_image(input_path, max_width, quality=80):
"""Converte JPG/PNG para WebP otimizado."""
img = Image.open(input_path)
# Redimensionar mantendo proporção
if img.width > max_width:
ratio = max_width / img.width
new_height = int(img.height * ratio)
img = img.resize((max_width, new_height), Image.LANCZOS)
# Salvar como WebP
output_path = os.path.splitext(input_path)[0] + '.webp'
img.save(output_path, 'WebP', quality=quality, method=6)
return output_path
# Uso — gerar versão desktop + mobile de cada imagem
optimize_image('fotos/foto-hero.JPG', max_width=800) # Desktop
optimize_image('fotos/foto-hero.JPG', max_width=480) # Mobile
Dimensões recomendadas:
<picture> com srcset<!-- HERO (above-fold) — fetchpriority="high" + preload -->
<picture>
<source media="(max-width: 768px)" srcset="fotos/foto-hero-mobile.webp" type="image/webp">
<source srcset="fotos/foto-hero.webp" type="image/webp">
<img src="fotos/foto-hero.JPG" alt="Descrição" width="800" height="1200" fetchpriority="high">
</picture>
<!-- BELOW-FOLD — loading="lazy" -->
<picture>
<source media="(max-width: 768px)" srcset="fotos/foto-about-mobile.webp" type="image/webp">
<source srcset="fotos/foto-about.webp" type="image/webp">
<img src="fotos/foto-about.JPG" alt="Descrição" width="900" height="1348" loading="lazy">
</picture>
<!-- No <head>, ANTES de qualquer CSS -->
<link rel="preload" as="image" type="image/webp" href="fotos/foto-hero-mobile.webp" media="(max-width: 768px)">
<link rel="preload" as="image" type="image/webp" href="fotos/foto-hero.webp" media="(min-width: 769px)">
Regras de imagem:
fetchpriority="high" — APENAS na imagem hero (above-fold)loading="lazy" — em TODAS as imagens below-foldwidth e height explícitos — SEMPRE (previne CLS)<img src> — compatibilidade com browsers antigosCSS monolítico bloqueia renderização. Separar em 3 partes:
<head>
├── <style> ... </style> → Critical CSS inline (~6KB max)
├── fonts.css (deferred) → @font-face declarations
└── styles-deferred.css (deferred) → Todo o resto
</head>
<style>)Incluir APENAS estilos do above-fold:
:root { --cores, --fontes })Meta: ~6KB max inline. Acima de 10KB já prejudica Speed Index.
<!-- Non-critical CSS -->
<link rel="stylesheet" href="styles-deferred.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="styles-deferred.css"></noscript>
O que vai no deferred:
O CSS inline no <style> DEVE ser minificado (uma linha, sem espaços extras). Remover comentários, quebras de linha e propriedades duplicadas.
Google Fonts via <link> externo é render-blocking. Self-host elimina isso.
latin e latin-ext (suficiente para PT-BR)/fonts/Regras críticas:
font-display: optional — NUNCA swap. Optional = zero CLS, browser usa fallback se fonte não carregou a tempo. Swap causa flash visível.unicode-range — subsetting automático, browser só baixa o que precisawoff2 apenas — suporte universal em browsers modernos<link rel="preload" as="style" href="fonts.css">
<link rel="stylesheet" href="fonts.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="fonts.css"></noscript>
Ordem no <head>:
<style>Para Vercel, configurar vercel.json:
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-XSS-Protection", "value": "1; mode=block" }
]
},
{
"source": "/fotos/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
]
},
{
"source": "/fonts/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
]
},
{
"source": "/(.*).html",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" }
]
}
]
}
Lógica (3 níveis de cache):
immutable, 1yr — nunca mudam sem novo filenamemust-revalidate — muda entre deploysmust-revalidate — sempre busca versão mais recente<script src="config.js?v=2" defer></script>
async para scripts que dependem de ordem. defer mantém ordem e não bloqueia parsing.?v=N como cache-busting quando o config muda.<head>Mover config/inicialização para arquivo externo com defer. JS inline no <head> aumenta o tamanho do HTML, que impacta TUDO no PageSpeed throttled.
defer + inline script (CRÍTICO)Quando há um <script src="config.js" defer> seguido de um <script> inline, o inline executa ANTES do defer. Variáveis definidas no config.js NÃO existem quando o inline roda.
Regra: NUNCA chamar funções que dependem de config.js no top-level do inline script. Sempre dentro de DOMContentLoaded.
<script src="config.js?v=2" defer></script>
<script>
// 1. Definir funções (não dependem de config.js)
function initPage() { /* usa LP_CONFIG */ }
// 2. Registrar via DOMContentLoaded (config.js já terá executado)
document.addEventListener('DOMContentLoaded', initPage);
// 3. TODO código que usa variáveis do config → dentro de DOMContentLoaded
document.addEventListener('DOMContentLoaded', () => {
// Countdown, form submit, smooth scroll, etc.
});
</script>
<!-- Injetar via JS após page load para não bloquear renderização -->
<script>
window.addEventListener('load', () => {
// Inicializar pixel aqui
});
</script>
font-display: swap com size-adjust = CLS PIORfont-display: optional. Browser usa fallback se fonte não carregou — zero CLS garantido.immutable em JS que muda = configs não atualizammust-revalidate. immutable APENAS para assets que nunca mudam.defer + inline script = variáveis não existem no top-levelDOMContentLoaded.No ambiente throttled do PageSpeed (4G: 1.6 Mbps, 150ms RTT), o tamanho do HTML impacta TODAS as métricas. Cada KB a mais no HTML atrasa FCP, LCP, Speed Index e TBT. Menor HTML = melhor score.
<picture> com <source> WebP + fallback JPGfetchpriority="high" APENAS na hero imageloading="lazy" em TODAS as imagens below-foldwidth e height explícitos em TODAS as <img><head>media="print" onload="this.media='all'"transition: all (especificar propriedades)font-display: optional (NUNCA swap)defer<head>DOMContentLoadedwindow.load?v=N cache-busting em scripts que mudamlp-otimizada/
├── index.html → HTML com critical CSS inline (~30-35KB)
├── obrigado.html → Página obrigado (mesmas otimizações)
├── styles-deferred.css → CSS non-critical (~12KB)
├── fonts.css → @font-face declarations (~7KB)
├── config.js → Configuração + pixel + init (defer)
├── fonts/ → woff2 self-hosted
├── fotos/ → WebP otimizado (desktop + mobile)
├── favicon.svg → Ícone SVG (< 1KB)
└── vercel.json → Cache headers + security + rewrites
<head><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>...</title>
<meta name="description" content="...">
<!-- 1. Preloads (fontes e hero image) -->
<link rel="preload" as="style" href="fonts.css">
<link rel="preload" as="image" type="image/webp" href="fotos/hero-mobile.webp" media="(max-width: 768px)">
<link rel="preload" as="image" type="image/webp" href="fotos/hero.webp" media="(min-width: 769px)">
<!-- 2. Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<!-- 3. Fonts deferred -->
<link rel="stylesheet" href="fonts.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="fonts.css"></noscript>
<!-- 4. Critical CSS inline (APENAS above-fold, ~6KB max) -->
<style>
/* Reset + variables + hero + header + CTA + form */
</style>
<!-- 5. Non-critical CSS deferred -->
<link rel="stylesheet" href="styles-deferred.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="styles-deferred.css"></noscript>
<!-- 6. OG/Twitter meta tags -->
<meta property="og:title" content="...">
</head>