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"])
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:
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));
}
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
Para o Mermaid não ficar com a paleta dele, as cores saem dos mesmos tokens do site, lidos em tempo de execução:
// 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();
}
--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
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":
-
Mais de um autor. Convenção que vive num
READMEnão sobrevive à segunda pessoa; aí um template obrigatório passa a valer mais que a liberdade. -
Publicação frequente. Se o texto sai toda semana, as
quarenta linhas de
<head>repetidas viram um imposto real. - 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
-
Em bloco
notede 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. ↩ -
O código deste site é aberto:
github.com/acwoss/woss.tech.
As convenções de publicação estão no
CLAUDE.mde no modelo comentado emdocs/. ↩