Engenharia · 3 diagramas

Um blog sem framework: o que ganhei e o que perdi

Este site é HTML escrito à mão, um arquivo por publicação, e um script que só gera o que é derivado. Eis as decisões, o que elas custam e os dois defeitos que só apareceram depois de renderizar.

A pergunta que decide a arquitetura de um blog pessoal não é "qual framework". É quantas vezes por ano você vai publicar. Com duas dúzias de textos, o custo de manter um pipeline de build supera o custo de escrever HTML — e o HTML não tem versão nem breaking change.

Então este site não tem framework. Cada publicação é um arquivo .html na pasta publicacoes/, servido direto pelo GitHub Pages. Não há transpilação, hidratação nem bundle. O que existe é um script de 380 linhas em Node, sem dependência alguma, que gera só o que é derivado: a lista de publicações, os cartões de tópico, o feed.xml e o sitemap.xml.

A liberdade que o arquivo solto dá

O ganho principal não é performance — é que cada texto pode ter a forma que o assunto pedir. Um ensaio sobre latência quer gráfico; um sobre contrato de API quer tabela larga; este quer diagrama. Num gerador de site estático isso vira briga com o template. Aqui é só escrever a marcação.

O preço dessa liberdade é a falta de componentes: o <head> de cada publicação repete umas quarenta linhas de meta. A resposta não foi inventar um template — foi fazer o build reclamar. Se falta description, se o canonical aponta para o arquivo errado, se o nome do arquivo não começa com a data de publicação, o build para e diz qual é o problema.

flowchart TD
    E["escreve o .html"] --> B["npm run build"]
    B --> V{"metadados completos?"}
    V -- "não" --> F["para e diz qual campo falta"]
    F --> E
    V -- "sim" --> G["gera lista, tópicos, feed e sitemap"]
    G --> O["npm run og"]
    O --> C["commit e push na main"]
    C --> P(["GitHub Pages publica"])
Publicar é isto. A caixa de decisão é a única coisa que me protege de subir um texto sem metadado — não há revisor.

Fonte única de verdade

A tentação óbvia num site assim é manter um posts.json com a lista de publicações. É também o jeito garantido de ter uma lista que discorda dos arquivos. Aqui a fonte de verdade são as próprias publicações: o script lê os meta de cada uma e deriva o resto.

Até o tempo de leitura é contado, não digitado — e conta só a prosa, porque ninguém lê um diagrama a duzentas palavras por minuto:

javascript · scripts/build.mjs
function minutosDeLeitura(html) {
  const corpo = html.match(/<article[^>]*>([\s\S]*?)<\/article>/i);
  if (!corpo) return null;

  const prosa = corpo[1]
    .replace(/<pre[\s\S]*?<\/pre>/gi, ' ')          // código e diagrama fora
    .replace(/<figcaption[\s\S]*?<\/figcaption>/gi, ' ')
    .replace(/<[^>]+>/g, ' ')
    .replace(/&[a-z]+;|&#\d+;/gi, ' ');

  const palavras = prosa.split(/\s+/).filter((p) => p.length > 1).length;
  return Math.max(1, Math.round(palavras / 200));
}
Regex em HTML é dívida técnica — aceitável aqui porque eu escrevo o HTML, o formato é meu e o build quebra alto quando não casa.

Tema claro e escuro sem duplicar CSS

Toda cor do site é um custom property no :root, com um bloco para o tema escuro. A troca de tema é um atributo no <html> — o CSS faz o resto sozinho.

Sozinho, exceto num lugar: diagrama. O SVG que o Mermaid gera carrega a cor embutida em cada forma, e não herda custom property. Trocar o tema exige redesenhar. É a única parte do site em que o JavaScript precisa saber que cor é a cor.

sequenceDiagram
    autonumber
    participant P as Pessoa
    participant S as site.js
    participant C as CSS
    participant M as Mermaid

    P->>S: clica no tema
    S->>C: grava data-theme
    Note over C: os tokens --mm-* mudam
    S->>M: dispara tema:mudou
    M->>C: lê as cores novas
    M->>M: redesenha
O evento existe para o site.js não precisar conhecer o Mermaid. Quem depende de cor se inscreve; quem não depende, ignora.

Para o Mermaid não ficar com a paleta dele, as cores saem dos mesmos tokens do site, lidos em tempo de execução:

javascript · js/publicacao.js
// o SVG carrega cor embutida: trocar o tema exige redesenhar
document.addEventListener('tema:mudou', function () { desenhar(); });

function cor(nome) {
  return getComputedStyle(document.documentElement)
    .getPropertyValue(nome).trim();
}
Os tokens do diagrama (--mm-node, --mm-line…) são cores sólidas de propósito: o Mermaid faz conta com elas para derivar tons, e derivar a partir de rgba() com alfa produz resultado errado.

Dois defeitos que só apareceram depois de renderizar

Nenhum dos dois aparece lendo o código. Os dois apareceram olhando a página pronta — que é o argumento a favor de sempre olhar a página pronta.

O <br/> que desaparece

No Mermaid 11.15, <br/> dentro de rótulo de nó não quebra a linha: a tag é removida e o espaço em volta é engolido. Um rótulo escrito "cabe na<br/>janela?" aparece na tela como cabe najanela?. Não muda com htmlLabels ligado nem desligado. A saída é escrever o rótulo corrido e deixar a quebra automática agir.1

A fonte que mente

Diagrama: encolher ou rolar

O Mermaid tem uma opção, useMaxWidth, que decide se o desenho encaixa na largura disponível ou fica no tamanho natural. Ela parece cosmética e não é: encaixar o SVG numa coluna encolhe o rótulo na mesma proporção. Um diagrama de 1750px numa coluna de 930px vira 43% do tamanho, e um rótulo de 12px vira um borrão de 5px.

A escolha aqui foi: no desktop, encaixar — diagrama sem moldura e sem barra de rolagem, parte do texto. Para isso o rótulo é definido grande (15px) e o diagrama é mantido estreito, de modo que na prática ele nem reduz. Abaixo de 900px a regra inverte: encaixar daria 6px, então o desenho volta ao tamanho natural e se arrasta com o dedo — gesto nativo, que no celular não desenha barra nenhuma.

Decisão Ganho O que custa
Um arquivo .html por texto Cada publicação tem a forma que o assunto pedir <head> repetido; o build valida no lugar do template
Lista de publicações em HTML estático Indexável e legível sem JavaScript Precisa ser regerada; a CI falha se esquecer
Metadados nas próprias publicações Nenhuma lista para discordar dos arquivos Regex em HTML no script de build
Mermaid por CDN Diagrama versionado como texto, no meio do texto 3,3 MB de JavaScript em quem abre uma publicação com diagrama
GitHub Pages a partir da main Publicar é git push Sem ambiente de staging; a revisão é o Pull Request

O ciclo de vida de uma publicação

Há um estado nessa máquina que quase todo blog esquece de desenhar, e é o mais importante: corrigida. Texto técnico envelhece, e afirmação que se provou falsa merece correção datada no próprio texto, não exclusão silenciosa.

stateDiagram-v2
    [*] --> Rascunho
    Rascunho --> Rascunho: build reclama
    Rascunho --> Revisão: build passa
    Revisão --> Publicada: merge na main
    Publicada --> Corrigida: achei um erro
    Corrigida --> Publicada: nota com a data
    Publicada --> [*]

    note right of Corrigida
        a correção fica
        no texto, datada
    end note
O laço Rascunho → Rascunho é o build reclamando de metadado faltando. O laço pela Corrigida é o que mantém o arquivo honesto com o tempo.

Quando isso é a escolha errada

Escrever HTML à mão deixa de valer a pena rápido em três situações, e nenhuma delas é "o site cresceu":

  1. Mais de um autor. Convenção que vive num README não sobrevive à segunda pessoa; aí um template obrigatório passa a valer mais que a liberdade.
  2. Publicação frequente. Se o texto sai toda semana, as quarenta linhas de <head> repetidas viram um imposto real.
  3. Conteúdo que vira dado. No momento em que você quer busca, listas relacionadas ou paginação por tag, você quer um índice — e um índice é um banco de dados esperando para nascer.

Nada disso se aplica a um blog pessoal com um autor e uma dúzia de textos por ano. Enquanto não se aplicar, o arquivo .html continua sendo a coisa mais durável que se pode escrever para a web.2


notas

  1. Em bloco note de diagrama de sequência ou de estado, a quebra de linha no próprio fonte funciona normalmente — é o jeito de ter nota em várias linhas.
  2. O código deste site é aberto: github.com/acwoss/woss.tech. As convenções de publicação estão no CLAUDE.md e no modelo comentado em docs/.

Anderson Woss

Engenheiro de software. Escrevo aqui sobre sistemas distribuídos que precisam continuar de pé e modelos que precisam parar de inventar.