# Plano 14 — SEO, Performance e Borda

> **Milestone:** M4 · **Depende de:** 13 · **Habilita:** 15
> **Objetivo:** o portal precisa ser indexável, rápido e disponível sob pico —
> requisitos de transparência e cidadania, para além de SEO comercial.

---

## 1. SEO técnico (via `src/services/Seo`) — ADR-027

**Service (headless, agnóstico — ADR-015):**
- Estado por request: title/description/keywords/canonical/cover/robots/`@graph`.
- `bindData(SEOData)` (readonly) a partir de `metadata['seo']` (**jsonb**, ADR-017); fallbacks
  globais (site title/description/logo) e **sufixo de título** (`Título | Entidade`).
- **Sem trait** (ADR-001); contrato no service, fake nos testes.

**Render component-driven:**
- **`<x-service-seo::tags>`** emite `<title>`, `meta description`, `canonical`, `robots`,
  OpenGraph/Twitter e o **JSON-LD `@graph`** — “fonte da verdade” na view do módulo.

**JSON-LD `@graph` (governamental):**
- `GovernmentOrganization` + `WebSite` (`SearchAction`) + `GovernmentPage` (com
  `accessibilityAPI`/`accessibilityFeature` — sinal eMAG) + schema do módulo.
- Módulos: `NewsArticle`, `Legislation`, `GovernmentService`, `BreadcrumbList`,
  **`Dataset`** (dados abertos — ADR-018).
- Imagem OG do service **Media** (ADR-026), 1200×630; `og:type`/`article:*` por conteúdo.

**Arquivos de raiz (controller nativo, cache Valkey):**
- `robots.txt` (Allow/Disallow por área + `Sitemap:`), `sitemap.xml` (índice + sitemaps por tipo,
  `lastmod`/`priority`, limite ~20k), **`/.well-known/security.txt`** e `manifest.webmanifest`.
- `Content-Type` correto + `Cache-Control`/`ETag` (ADR-022).

**Erros, redirects e indexação:**
- **404 real** (nunca soft-404) + log de link quebrado + `X-Robots-Tag: noindex`.
- **301 redirects** (tabela + middleware) preservando o legado (plano 15) e o “link juice”.
- Indexação ativa **nativa**: ping de sitemap / **IndexNow** (HTTP); Google Indexing API → backlog.

**i18n/SEO (ADR-012):** `hreflang` alternates + canônica **por locale** + `<html lang>`.

**Analytics privacy-first (ADR-019):** **sem Google Analytics por padrão**; ranking interno via
**Valkey HyperLogLog** (IP hasheado, sem PII) → `seo.page_stats`; gtag opcional com consentimento.

**CMS:** **`<x-service-seo::input>`** (preview de snippet + contadores 60/160 + JSON-LD avançado),
**nativo/daisyUI, sem jQuery**.

**Semântica e a11y** alimentam o SEO (plano 11): headings, landmarks, skip links, alt.

## 2. Performance de entrega

- **Imagens**: AVIF/WebP com `<picture>`, `srcset`, `sizes`, dimensões explícitas
  (`width`/`height`) para evitar CLS; `loading="lazy"` abaixo da dobra e
  `fetchpriority="high"` + `decoding="async"` na imagem de LCP.
- **Caminho crítico**: minimizar CSS/JS que bloqueiam render; **CSS crítico inline**;
  `preconnect`/`dns-prefetch` das origens essenciais; `defer` nos scripts.
- **Fontes**: `font-display: swap`, preload das fontes críticas e subset.
- **CSS/JS**: bundle por área (portal × CMS), versionado, `defer`; sem jQuery.
- **Modo otimizado** (ADR-008): `php artisan optimize` + OPcache ligado; todo benchmark é
  medido no build de produção, nunca em modo debug/dev.
- **Cache de assets**: hash imutável (`Cache-Control: immutable`) +
  `stale-while-revalidate`; Brotli preferível a gzip.
- **Cache de aplicação**: respostas públicas via Valkey com tags e invalidação por evento.
- **Main-thread livre**: PDFs/imagens/notificações em fila (workers).
- **Banco**: eager loading e índices (GIN/full-text); N+1 detectado em teste.
- **Edge page cache** (trilha opcional): cache no proxy/Apache para HTML de páginas quentes.
- **PWA** (ADR-010): `manifest.webmanifest` (name/short_name, ícones 192+512, `start_url`,
  `display`, `prefer_related_applications:false`) + service worker (cache de assets +
  **fallback offline**) + instalabilidade; ícones e `theme-color` do tema do tenant; SW
  registrado após `load`, com fluxo de atualização.
- **Core Web Vitals** como alvo: LCP ≤2,5s, INP ≤200ms, CLS ≤0,1 (p75, mobile), medidos
  **em campo (RUM)** e no CI; evitar long tasks >50ms.
- **Orçamento de performance** no CI: limite de queries por página, tamanho de bundle,
  **orçamento de JS/HTML do portal** (mínimo JS) e LCP/INP/CLS em página-chave.

## 3. Observabilidade de performance

- Métricas p95/p99 por rota, cache hit ratio, tamanho de fila (plano 06).
- SLOs simples: disponibilidade do portal e latência de páginas quentes.

## 4. Atividades

- [ ] `src/services/Seo`: DTO, renderer, JSON-LD `@graph`, sitemap/robots/security.txt + testes.
- [ ] `<x-service-seo::tags>` (render) e `<x-service-seo::input>` (CMS) nativos/daisyUI.
- [ ] Tabela `redirects` + middleware de 301; 404 real + log de link quebrado.
- [ ] Ranking privacy-first (Valkey HyperLogLog) → `seo.page_stats`; gtag opcional.
- [ ] `hreflang`/canônica por locale (ADR-012) e schema `Dataset` (ADR-018).
- [ ] Helper/componente de metadados nas views de portal.
- [ ] Componente de imagem responsiva integrado ao `src/services/Media`.
- [ ] Cache de página com invalidação por evento (`NoticiaPublicada` → zera tag).
- [ ] Filas para mídia/PDF/notificações + worker no compose.
- [ ] Teste anti-N+1 (assert de query count) nas listagens.
- [x] Orçamento de performance no CI (bundle + queries + LCP em rota-chave), medido
      sob `php artisan optimize` e OPcache ativo (ADR-008).
      Feito: **bundle** (`.br`/`.gz` + tamanho) e **queries** (anti-N+1) em `PerformanceBudgetTest`;
      **CLS** e recursos bloqueantes em `src/tests-e2e/tests/11-performance.spec.ts`; LCP/INP são de
      campo (RUM) — medir tempo de parede no CI seria flaky (ADR-001).
- [ ] Documentar política de cache e invalidação.
- [ ] PWA: manifest + service worker + ícones/`theme-color` pelo tema do tenant.

## 5. Testes e evidências

- [ ] Sitemap contém só URLs públicas válidas; robots bloqueia áreas privadas.
- [ ] JSON-LD `@graph` válido (Rich Results Test) com acessibilidade sinalizada.
- [ ] URL inexistente → `404` (não 200) + log; slug antigo → `301`.
- [ ] `security.txt`/`manifest.webmanifest` com tipo correto; `hreflang` por locale.
- [ ] Nenhuma chamada a Google Analytics por padrão; ranking sem PII.
- [ ] Teste: publicação invalida exatamente as tags afetadas (assert no cache).
- [x] Teste: listagem com N itens executa número constante de queries.
- [x] Core Web Vitals (LCP/INP/CLS) da home dentro do orçamento (p75 mobile).
      Feito no CI: **CLS ≤ 0,1** (E2E). LCP/INP ficam para o RUM (campo).
- [ ] `<picture>` serve AVIF/WebP com fallback e dimensões corretas.
- [ ] Manifest válido e SW instalável; a página funciona sem SW (e sem JS).
- [x] Orçamento de JS do portal dentro do limite (mínimo JS).

## 6. Definition of Done

- [ ] Portal indexável (sitemap/robots/JSON-LD) e rápido dentro do orçamento.
- [ ] Cache com invalidação comprovada por teste.
- [ ] Nenhuma rota quente com N+1.

## 7. Riscos

| Risco | Mitigação |
|---|---|
| Cache servir conteúdo obsoleto | Tags + invalidação por evento testada |
| Otimização prematura | Orçamento objetivo e medição antes/depois |
| SEO quebrar URLs do legado | Mapa de redirects validado no plano 15 |
