2

Como levei um blog em PHP puro de 84 para 97 no PageSpeed (e a acessibilidade para 100)

CONTEÚDO (cole no editor, em Markdown):

Tenho um blog em PHP 8 + MySQL + Bootstrap 5, sem framework, em cPanel atrás do Cloudflare. O PageSpeed no celular:

MétricaAntesDepois
Desempenho (home)8497
Acessibilidade90100
CLS0,0830
Requisições bloqueando a renderização1.650 msnenhuma

Sem reescrever o site, só ajustes pontuais. Serve para qualquer site PHP com Bootstrap.

1. O Bootstrap inteiro era o maior vilão

O bootstrap.min.css tem cerca de 230 KB, e mais de 90% não era usado. E vinha de outro domínio (jsDelivr), somando DNS + TLS antes da primeira pintura.

O que fiz:

  1. Baixei o Bootstrap para o próprio domínio.
  2. Rodei o PurgeCSS sobre os arquivos PHP para gerar só as classes usadas:
npx purgecss --css assets/vendor/bootstrap.min.css \
  --content "*.php" "templates/*.php" \
  --output assets/vendor/purged \
  --safelist show collapsing collapse fade active
  1. Resultado: 230 KB → 37 KB. Pequeno o suficiente para ir inline no <head>, eliminando a requisição bloqueante:
<?php $css = __DIR__ . '/../assets/vendor/purged/bootstrap.min.css'; ?>
<?php if (is_file($css)): ?>
<style><?= file_get_contents($css) ?></style>
<?php else: ?>
<link rel="stylesheet" href="/assets/vendor/bootstrap.min.css">
<?php endif; ?>

O else garante que o site nunca fica sem estilo.

Cuidados:

  • A safelist precisa das classes adicionadas pelo JavaScript do Bootstrap e das que vêm do conteúdo no banco. O PurgeCSS só enxerga os arquivos.
  • No Windows, o PurgeCSS 8 não carregou o config (Received protocol 'c:'). A linha de comando resolveu.
  • Toda classe nova num PHP exige rodar o PurgeCSS de novo.

2. Fontes e ícones fora do caminho crítico

Google Fonts e Bootstrap Icons passaram a carregar de forma assíncrona:

<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap"
      media="print" onload="this.media='all'">

3. CLS zerado com fonte substituta de métricas ajustadas

A troca de fonte fazia o texto "pular". Solução: fallback com as métricas da Inter:

@font-face {
  font-family: "Inter Fallback";
  src: local("Arial");
  size-adjust: 107.12%;
  ascent-override: 90.44%;
  descent-override: 22.52%;
  line-gap-override: 0%;
}
body { font-family: "Inter", "Inter Fallback", system-ui, sans-serif; }

Somando width/height nas imagens, o CLS foi de 0,083 para 0.

4. LCP

Preload da capa com fetchpriority="high" e media para celular e desktop.

5. Acessibilidade: quase tudo era contraste

Quase tudo era cor que parecia ok a olho nu:

  • O vermelho da marca (#e63946) dava 4,2:1 sobre branco. O mínimo é 4,5:1. Troquei por #d62839, visualmente quase igual.
  • O azul de link do Bootstrap (#0d6efd) dava 4,3:1 sobre #f8f9fa. Três variáveis resolveram:
:root {
  --bs-link-color: #0a58ca;
  --bs-link-color-rgb: 10, 88, 202;
  --bs-link-hover-color-rgb: 8, 66, 152;
}
  • text-muted do Bootstrap 5.3 sobre fundo escuro (rodapé) fica quase invisível.
  • O resto: <label> em campos só com placeholder, aria-label em botões de ícone e ordem dos títulos.

6. Cache: o .htaccess não estava valendo

O PageSpeed mostrava 4 horas de cache no JS, o padrão do Cloudflare: o servidor não enviava o cabeçalho. Resolvi com uma Cache Rule no Cloudflare por extensão, com Browser TTL de 1 ano.

O que aprendi

  1. Medir antes de mexer. Os insights do Lighthouse valem mais que a nota.
  2. O maior ganho foi o CSS, não imagens nem JavaScript.
  3. Acessibilidade é, em grande parte, contraste. Vale testar as cores da marca logo no começo do projeto.
  4. Rode o PageSpeed 2 ou 3 vezes. A variação entre execuções é grande.

Alguém já usou PurgeCSS com conteúdo vindo do banco? Como resolveram a safelist?

Carregando publicação patrocinada...
1