Construindo o Monney Works — do mock ao deploy
A arquitetura por trás de um blog pessoal sério, feito do jeito que eu acredito
Fiquei uns dias olhando um .zip no meu diretório. Era um mock que eu tinha gerado no V0 da Vercel para testar a ideia do blog: uma Omni Search com glassmorphism centralizada, lista de artigos ocupando dois terços da tela, sidebar de tópicos à direita, tudo em preto e branco, elegante, minimalista. Exatamente o visual que eu queria.
Aí abri o código.
Mock data hardcoded em lib/mock-data.ts. ignoreBuildErrors: true no next.config. i18n resolvido como um Context com useState. Zero testes. Nenhuma camada de domínio. A estrutura era uma amostra visual — e é exatamente para isso que um mock serve — mas eu não ia colocar aquilo em produção como base de um projeto meu.
Decidi aproveitar o que importava (componentes, estrutura, decisões visuais de UX) e reconstruir tudo por baixo.
O que eu queria do blog
Antes de escrever qualquer linha, o importante é a ideia. A minha era clara:
- Minimalista e elegante. Preto e branco, sem firula. Foco no texto, nos componentes modernos, no "vidro translúcido" da Omni Search fixa em todas as páginas.
- Markdown-first. Eu escrevo rascunhos soltos — podem ser notas, pensamentos corridos, a documentação de um MVP que acabei de terminar — jogo num diretório privado, e depois rodo uma skill que processa o rascunho: sugere tags, gera slug, traduz para o outro idioma, encontra artigos relacionados, e move tudo para a pasta cronológica certa. Meu trabalho é gerar os arquivos. O resto é automatizado.
- Bilíngue desde o primeiro dia. pt-BR e inglês, URLs localizadas (
/pt-BR/...e/en/...), cada artigo vivendo como dois arquivos (pt-BR.md+en.md) no mesmo diretório. Espanhol e português de Portugal vêm depois, mas a arquitetura já precisa aguentar. - Git-based admin. Só eu tenho acesso de administrador. Mas "acesso" aqui não é login em painel — é
git commit+git push. A Vercel rebuilda. Acabou. Nenhuma auth, nenhum banco, nenhum dashboard. - Público e open source. O código fica no GitHub. O blog é uma vitrine viva dos meus padrões — se eu prego DDD e TDD no dia a dia, o meu próprio blog precisa ter DDD e TDD.
Sobre o conteúdo: quero escrever sobre engenharia de software e arquitetura, claro, mas também sobre projetos MVP que eu desenho a partir de vagas de emprego reais (pego os requisitos técnicos, arquiteto a aplicação completa seguindo os padrões que acredito, e deixo rodando em produção pra quem quiser testar), e sobre marketing, vendas, estratégia, persuasão, filosofia, comunicação, liderança — tudo o que me interessa pensar fundo.
Por que não usar o V0 como está?
Porque o V0 é excelente para mockar e péssimo como base de produção. O código que ele gera é suficiente para você navegar e validar UX — e foi exatamente isso que fez por mim. Ver a Omni Search funcionando na tela me convenceu de que a ideia valia a pena. Mas a arquitetura do mock assume coisas que eu não aceito em projeto que vai pra frente:
- Que dados mockados são o suficiente ("é só trocar depois").
- Que ignorar erros de typecheck no build é aceitável.
- Que um
Context+useStateé i18n. - Que testes são opcionais.
Cada uma dessas decisões, se você deixar passar, vira dívida técnica na primeira semana. Eu prefiro pagar o custo upfront — principalmente num projeto onde eu sou o único dono.
Como eu reconstruí
O plano foi executar em fases pequenas, cada uma com no máximo cinco arquivos, commit granular, verificação (npx tsc --noEmit, npm test, npm run lint) antes de avançar. Na prática ficou assim:
Fase 0 — Bootstrap. Next.js 16, React 19, TypeScript strict com noUncheckedIndexedAccess, noUnusedLocals e noUnusedParameters ligados. Tailwind v4. ESLint flat config. Prettier. Vitest. Sem ignoreBuildErrors. A primeira coisa que eu faço num projeto novo é garantir que o typecheck é levado a sério.
Fases 1 e 2 — Domínio. Aqui é onde eu mais me diverti. Modelei o Article como aggregate root, identificado pelo Slug. Todos os value objects — Slug, Tag, Category, PublishDate, Locale, LocalizedText, ReadingTime — têm construtor privado e factory estática, validando invariantes no construtor. Isso significa que um Article em memória nunca está em estado inválido. Não existe Article com tags vazias, com categoria desconhecida, com data ruim. Se a construção passa, o objeto é confiável do ponto em diante.
Escrevi 92 testes unitários durante a modelagem, seguindo TDD estrito: vermelho, verde, refactor. O teste veio primeiro, sempre.
Os contratos ArticleRepository e ArticleIndex vivem no domínio como puros tipos, sem implementação. A inversão de dependência aqui é literal: o domínio não sabe se os artigos vêm de arquivos markdown, de um Postgres ou de uma API remota.
Fase 3 — Infraestrutura de markdown. Um anti-corruption layer (frontmatter-parser.ts) pega um par de arquivos (pt-BR.md + en.md), usa o gray-matter para extrair o YAML, e traduz para os value objects do domínio. Se o frontmatter estiver ruim — categoria fora do enum, tag vazia, data inválida — o parser explode na borda, antes de um objeto sujo chegar ao domínio. Esse é o ponto inteiro de ter uma ACL: o lixo do mundo externo para aqui.
O markdown-article-repository.ts implementa o contrato do domínio varrendo content/articles/YYYY/MM/{slug}/, lendo os dois arquivos em paralelo com Promise.all, e propagando erros com o caminho do diretório que falhou — pra facilitar descobrir qual artigo quebrou o build.
Fase 4 — Use cases. Cinco casos de uso finos: listArticles, getArticleBySlug, searchArticles, findRelatedArticles, listTopics. Todos recebem o repositório por injeção. Cada teste usa um fake in-memory em vez de tocar o filesystem. Resultado: a suite roda em menos de dois segundos mesmo com 137 testes.
Fase 5 — i18n. next-intl com routing baseado em rotas (/pt-BR/... e /en/...). A lista de locales suportados vem direto do domínio (LOCALES do locale.ts), então não existe a possibilidade de o routing estar desalinhado do resto da aplicação.
Fase 6 — Layout. Scaffold do App Router com NextIntlClientProvider, fontes Geist, header e footer. O next build gerou as primeiras rotas estáticas. Nenhum artigo ainda.
Fase 7 — Esta tela que você está vendo agora. Componentes do V0 portados para cima do domínio real. ArticleCard, ArticleList. Um composition root (src/infrastructure/container.ts) que instancia o repositório com process.cwd()/content/articles e expõe os use cases prontos para a home consumir. Um ArticleView (DTO) que achata o LocalizedText para o locale atual antes de cruzar a fronteira servidor → cliente — classes do domínio nunca serializam direto.
É aqui que o circuito se fecha.
O workflow que eu queria
O que me fez querer construir isso desse jeito foi a visão do fluxo de publicação. Funciona assim:
- Tenho uma ideia. Abro um arquivo em
content/_drafts/(um diretório git-ignored, privado, dentro do próprio repo). Jogo dentro as notas soltas, um rascunho de projeto, trechos de um MVP que estou desenhando — o que for. - Quando está pronto, rodo
/publish-article content/_drafts/foo.mdno Claude Code. - A skill lê o rascunho, sugere
slug,categoryetags, traduz para o outro idioma preservando code blocks e termos técnicos, calcula o tempo de leitura, encontra artigos relacionados por overlap de tags, e me mostra um preview completo. - Eu aprovo. A skill move os arquivos para
content/articles/2026/04/{slug}/pt-BR.md+en.md, rodanpm run typecheckenpm test, e pergunta se quero commitar. git push. Vercel rebuilda. Artigo no ar.
Meu trabalho é o que eu sei fazer: ter ideias e rascunhá-las. A skill cuida de tudo o que é automatizável. Nenhum painel admin, nenhum dashboard, nenhum login.
O que vem a seguir
Este artigo existe porque a plataforma agora consegue renderizá-lo. A Fase 7 é o primeiro momento em que tudo está conectado — o domínio, o parser, o repositório, o use case, a página do Next, os componentes portados. Se você está lendo isso, o circuito fechou.
O que ainda falta para o MVP completo:
- Topics sidebar na lateral direita com filtro por categoria.
- Omni Search funcional com busca textual e filtragem por tag.
- Página de artigo individual com MDX e componentes custom (callouts, embeds de vídeo, syntax highlighting de código).
- A skill
/publish-articleem si, que hoje é um plano no código e precisa virar um script real. - Deploy na Vercel.
Depois disso eu começo a postar o que sempre quis postar — os MVPs a partir de vagas reais, as reflexões sobre arquitetura, os rabiscos sobre vendas e persuasão, os pensamentos filosóficos que aparecem às três da manhã.
Mas o mais importante já aconteceu: o blog existe, é meu, está feito com os padrões que eu acredito, e agora posso escrever nele do jeito que eu sei escrever.
É isso. Obrigado por ler até aqui.