Alguns dos projetos em que eu trabalho são de clientes. Outros envolvem código que não faz sentido publicar porque ele é parte do próprio produto. Em quase todos, o que eu fiz fica fechado, e para quem olha de fora sobra só uma landing page ou uma linha no currículo. O que eu quero preservar aqui é o que não aparece nessas coisas: por que tomei cada decisão, quais problemas encontrei e o que considerei antes de escolher um caminho.
Foi por isso que criei o How I Build. O primeiro projeto que decidi documentar foi o próprio site, porque eu queria testar a ideia em algo que pudesse mostrar por completo, incluindo o código. O repositório reúne o template que dá estrutura ao site e o conteúdo que eu escrevo sobre os projetos. Mantive as duas partes separadas para que o template não precise saber nada sobre mim ou sobre o conteúdo publicado. As decisões abaixo começam, em grande parte, dessa separação.
Um repositório, duas branches
O template e o conteúdo do site precisam evoluir juntos, mas não deveriam ficar misturados. Eu queria poder usar o mesmo repositório para desenvolver o template sem colocar meu conteúdo dentro dele, e ao mesmo tempo manter o site que publico aqui atualizado com essas mudanças.
Decisão
Fiquei com um repositório e duas branches. A main contém o template e a site contém o conteúdo. Um workflow faz o merge de main → site a cada push, mantendo o site atualizado sem precisar repetir as mudanças manualmente.
Trade-offs
- Toda mudança no template passa por dois merges antes de chegar em produção.
- Arquivos que as duas branches precisam alterar, como a configuração e o índice de conteúdo, podem gerar conflitos. Nesses casos, a site sempre vence.
- Quem clona o template também recebe uma segunda branch que talvez não precise. Por isso, o workflow simplesmente não faz nada quando essa branch não existe.
O idioma padrão não tem prefixo
Eu queria que o inglês fosse o idioma padrão do site, mas sem colocar /en em todas as URLs. Ao mesmo tempo, o português precisava continuar disponível em um caminho próprio. Isso deixava duas alternativas: prefixar os dois idiomas, ou não diferenciar os idiomas na URL. A primeira tirava a possibilidade de usar / para o inglês; a segunda criava problemas de indexação, compartilhamento e geração de páginas estáticas.
Decisão
Inglês responde em /projects, português em /pt/projects. /en/* redireciona para o caminho sem prefixo,
então cada página tem exatamente um endereço. Saiu na v0.4.0.
Trade-offs
- O proxy fica mais complexo que um redirect simples: rewrite, redirect canônico e preferência lida só na raiz
- Montar um link passa a exigir um helper em vez de concatenar string
- A preferência do leitor vale só em
/. Um link profundo compartilhado abre no idioma em que foi compartilhado; isso é deliberado, mas pode surpreender
O tema é aplicado antes da página pintar
O tema precisava estar correto antes da primeira pintura da página. Ler o cookie no servidor parecia uma solução natural, mas criava dois problemas: a primeira visita não tem cookie, então quem prefere o tema escuro poderia ver um flash branco, e a leitura do cookie também tirava as rotas da renderização estática.
Decisão
Trade-offs
- Um script inline no documento, além de
suppressHydrationWarningna<html> - O
next/scriptnão serve: ele adia conteúdo inline para depois da primeira pintura, que é justamente o que esta solução precisa evitar - O
aria-pressedsó fica correto durante a hidratação, porque o servidor realmente não sabe a preferência do leitor
Sem banner de cookies
O site guarda duas coisas: um cookie com o idioma escolhido e uma entrada de localStorage com o tema escolhido. As duas só são escritas quando o leitor usa o controle que as define.
Isso me levou a uma pergunta simples: o que exatamente o leitor estaria recusando? Se ele escolhe português, por exemplo, eu preciso guardar essa escolha para lembrar dela depois. Se ele recusar esse armazenamento, a funcionalidade que acabou de pedir deixa de funcionar.
Decisão
Sem banner. Uma página de privacidade no lugar: o que é guardado, por quê e como apagar. Saiu na v0.16.0.
Trade-offs
- Colocar analytics depois significa adicionar o banner e reescrever aquela página
- Quem espera um banner pode interpretar a ausência como esquecimento, e não como uma decisão
Um workflow que nunca rodou
O workflow que faz o merge da main na site foi escrito para disparar em release: published. Ele nunca rodou uma vez. Não falhou — nunca começou. Não houve execução vermelha nem notificação; a branch de conteúdo simplesmente ficou para trás.
O GitHub não inicia workflows a partir de eventos criados pelo GITHUB_TOKEN, e o release-please publica as releases usando esse token.
A parte desconfortável é que eu já tinha usado essa mesma propriedade de propósito, uma issue antes, para impedir que o workflow de release entrasse em loop com o próprio commit. O mecanismo era conhecido; eu só o apliquei ao contrário.
Decisão
Disparar em push na main, que é uma ação humana. Corrigido na v0.17.1.
Trade-offs
- O sync passa a rodar a cada merge, e não a cada release. Isso acontece mais vezes do que o desenho original, mas mantém as branches mais próximas.
- O commit do próprio release-please continua não disparando o workflow, então um bump de versão espera o próximo merge.
O blog mora dentro do mesmo site
Nem tudo que eu quero escrever é um case. Tem opinião, tem coisa nova que eu testei e quis registrar, e tem nota curta que não sustenta um registro de decisões inteiro. Eu queria um lugar meu para isso, e a saída óbvia era abrir um blog separado em alguma plataforma pronta, deixando este site só com os projetos.
Decisão
Trade-offs
- Um post só entra no site quando existe em português e em inglês. O build recusa um texto que só tem um idioma, o que protege os cases e cria atrito justamente na nota curta.
- As tags são escritas por idioma, então
/blog/tags/processoe/blog/tags/processsão páginas diferentes. A troca de idioma só encontra a equivalente porque ela ocupa a mesma posição nos dois arquivos, e quem garante isso é o build. - Sete releases seguidas foram para o blog antes de ele ter conteúdo meu de verdade. O que sustenta essa decisão daqui em diante é escrever, e essa parte não depende de código.