Como construí o Vavito Archives? — Vavito Archives
Como construí o Vavito Archives?
Da ideia ao produto final: como desenvolvi meu blog de ponta a ponta
João Victor
·11 min de leitura·8 visualizações
O Vavito Archives é um site onde publico sobre desenvolvimento de software, arquitetura, produto e o que aprendo construindo projetos. Só que, para publicar esses textos do jeito que eu queria, precisei construir mais do que uma página com uma lista de artigos.
O site tem busca, tópicos, contas de leitores, comentários, artigos salvos, newsletter e um painel para criar e administrar o conteúdo. Também precisava funcionar bem no celular, ser acessível e continuar rápido mesmo nas páginas com imagens e artigos longos.
Neste texto, vou mostrar como organizei essas partes e algumas decisões que fizeram mais diferença durante o desenvolvimento.
Primeiro, a experiência de quem lê
A maior parte das pessoas vai chegar ao site para ler um artigo. Por isso, a navegação pública precisava ser simples: encontrar um texto pela Home, pela busca ou pelos tópicos e conseguir lê-lo sem a interface disputar atenção com o conteúdo.
A identidade visual segue essa ideia. Usei fundo escuro, poucos efeitos, Inter no texto e JetBrains Mono em detalhes pontuais. A coluna dos artigos fica próxima de 640 px de largura em telas maiores. Os previews seguem sempre a mesma ordem: capa, título, resumo, tópicos e informações como data e tempo de leitura. Assim, quem está percorrendo a página consegue comparar os artigos sem precisar decifrar um card diferente a cada seção.
Também pensei nos momentos em que os dados ainda não chegaram. As páginas públicas mostram partes do conteúdo progressivamente, com estados de carregamento para imagens e seções da página. Se uma requisição falhar, a interface apresenta uma mensagem compreensível em vez de despejar um erro técnico na tela.
Depois da leitura, entram as ações de quem tem uma conta: comentar, reagir e salvar artigos. Essas ações exigem autenticação, mas o visitante continua podendo acessar o conteúdo público.
Como as peças se comunicam
Eis que adentramos a parte técnica do produto.
O projeto está em um monorepo com PNPM e Turborepo. A aplicação web usa Next.js; a API usa NestJS. Entre elas há um cliente HTTP tipado, gerado a partir do contrato OpenAPI.
O frontend monta as páginas e cuida da interação. A API decide se uma operação é permitida, aplica as regras e acessa os dados. O PostgreSQL, hospedado no Supabase, guarda o estado da aplicação. Supabase Auth cuida das contas; Storage guarda avatares e imagens; Resend envia emails.
Carregando comentários…
Carregando artigos relacionados…
Essa separação também aparece dentro do código. Na API, controllers tratam a entrada e a saída HTTP, services coordenam os casos de uso e repositories concentram o acesso aos dados. As regras de domínio ficam fora dos detalhes de NestJS e Prisma. No frontend, as rotas ficam no App Router, enquanto as funcionalidades são organizadas por área do produto.
O cliente gerado pelo OpenAPI evita que cada tela monte requisições por conta própria. Quando o contrato da API muda, os tipos usados pelo frontend precisam acompanhar essa mudança. Parece um detalhe pequeno, mas é o tipo de coisa que ajuda a encontrar um problema antes de ele aparecer para quem está usando o site.
Login não é permissão para administrar
O Supabase Auth gerencia cadastro, login, confirmação de email e recuperação de senha. A API recebe o token da sessão e valida se ele é legítimo.
Mas estar logado não dá acesso ao painel. Para uma ação administrativa, a API consulta a role registrada no Profile da aplicação:
const role = await this.profileAuthorizationRepository.findActiveRoleByProfileId(
request.user.id,
);
if (!role || !acceptedRoles.includes(role)) {
throw new ForbiddenAccessException();
}
A distinção é simples: o token identifica a pessoa; o Profile informa o que ela pode fazer. A autorização não depende de metadata que o usuário consiga alterar no próprio cadastro.
O editor foi a parte que mais exigiu pensar em estados
No painel, o administrador escreve com Tiptap. O conteúdo é salvo como JSON, com suporte a títulos, listas, citações, links, código e imagens. A capa fica separada do corpo do artigo, porque também é usada nos cards, nos artigos relacionados e nos metadados de compartilhamento.
O ponto mais delicado é editar algo que já está publicado. Se cada autosave alterasse o artigo público, uma frase pela metade poderia aparecer para os leitores. Por isso, a edição de um artigo publicado vai para uma versão pendente. O conteúdo visível só muda quando o administrador escolhe Publicar alterações. A versão anterior é preservada como revisão.
Esse fluxo resolveu uma questão de produto, não apenas de implementação. O administrador pode trabalhar com autosave sem transformar o processo de escrita em uma publicação contínua.
Imagens e emails não terminam no botão “Enviar”
Uma imagem editorial passa pela API antes de ir para o Supabase Storage. O servidor confere o tamanho, a extensão, o MIME e o formato real do arquivo. Depois corrige a orientação, limita as dimensões e converte o resultado para WebP. Só então o arquivo é armazenado e associado a um MediaAsset.
Também existe um cuidado com falhas no meio do caminho. Banco de dados e Storage são serviços diferentes: salvar em um não garante que o outro salvou. O fluxo registra o estado do upload e tenta compensar falhas para não deixar o sistema acreditando que uma imagem está pronta quando ela não está.
Na newsletter aparece um problema parecido, mas com outra consequência: enviar duas vezes. Uma campanha guarda um retrato dos artigos selecionados, para que uma edição posterior não mude um email já preparado. O disparo usa uma chave de idempotência; repetir a mesma requisição não deve iniciar outro envio. Cada destinatário tem seu registro de entrega, atualizado depois pelos webhooks do Resend.
Com um artigo, o email pode levar o conteúdo completo. Com vários, usa previews. É uma decisão editorial inspirada na entrega de emails do Substack e que também influencia o modelo de dados e o processo de envio.
SEO e como os artigos aparecem nas buscas
Cada artigo publicado gera seu próprio título, descrição, URL canônica, dados estruturados e imagem para compartilhamento. Se eu não preencher os campos específicos de SEO no editor, o site usa o título e o resumo do artigo. Já páginas como login, perfil e administração recebem noindex e ficam fora do sitemap.
O sitemap é montado com as páginas públicas e os artigos publicados. Durante a implementação, encontrei um erro que passaria fácil despercebido: ele pedia 100 artigos por vez, mas a API aceitava no máximo 24. O resultado era um sitemap válido, só que sem os artigos. Ajustei a paginação para respeitar o limite da API e incluí um fallback para manter as páginas institucionais disponíveis caso ela esteja fora do ar.
Também configurei o domínio no Google Search Console e trabalhei a performance das páginas públicas, principalmente carregamento progressivo e imagens. Há mais decisões nessa parte — como JSON-LD, Open Graph e controle de indexação — que merecem um artigo próprio e que com certeza farei no futuro. Aqui, o ponto é que publicar o conteúdo e permitir que ele seja encontrado fizeram parte do mesmo trabalho.
Testar o site inteiro, não só funções isoladas
O projeto tem testes unitários e de integração da API, testes de componentes e integração do frontend e jornadas no navegador com Playwright. Essas jornadas passam por situações que uma pessoa realmente encontra: navegar, buscar, criar uma conta, comentar e, no painel, escrever e publicar um artigo.
O pipeline também verifica formatação, lint, tipos, build, contrato OpenAPI, segurança, acessibilidade e performance. Os checks da API e da Web alimentam um terceiro check, o Deploy Gate. O repositório ainda inclui uma verificação para que os builds de produção consultem o resultado da CI para o commit que está sendo publicado.
Na prática, isso diminui a chance de uma mudança aparentemente pequena quebrar outra parte do produto. Uma alteração no contrato da API, por exemplo, precisa continuar compatível com o cliente usado pelo frontend.
Da main para produção
O site roda em serviços diferentes: o frontend fica na Vercel, a API na Render, o Supabase hospeda banco, autenticação e imagens, e o Resend cuida dos emails. Essa divisão também influencia o deploy: uma mudança na interface pode depender de uma rota nova na API ou de uma alteração no banco.
Antes de publicar, a mudança passa pelos checks da API e da Web no GitHub Actions. Um terceiro check, o Deploy Gate, só aprova quando os dois terminam com sucesso. Os builds de produção também conferem o resultado da CI para o commit exato da main que estão prestes a publicar. Na Render, o build da API aplica as migrations pendentes com prisma migrate deploy; depois do deploy, os endpoints de health e readiness ajudam a conferir se a aplicação iniciou e consegue acessar o banco.
Tem um detalhe importante nesse processo: voltar o código para uma versão anterior não desfaz uma migration já aplicada. Por isso, mudanças no banco precisam ser revisadas pensando também na versão da API que ainda pode estar no ar durante o deploy ou um rollback. É menos empolgante do que apertar “publicar”, mas evita que uma atualização simples vire um problema de produção.
A documentação veio antes do código — e continuou durante ele
Antes de começar a implementação, eu precisava saber o que estava construindo. Defini o escopo da V1, separei o que ficaria para depois e documentei os três tipos de acesso: visitante, leitor e administrador. Também registrei as regras de negócio, os estados de entidades como artigos e comentários, o modelo de dados e um contrato inicial da API. Isso evitou decidir, no meio de uma feature, coisas básicas como quem pode executar uma ação ou o que acontece quando um artigo é despublicado.
Essa documentação não ficou congelada depois do primeiro commit. Conforme os fluxos saíram do papel, acrescentei guias mais próximos da implementação: autenticação, banco e migrations, envio de emails, testes, segurança e deploy, entre outros. Nem toda decisão inicial sobreviveu intacta, e tudo bem. Quando a implementação mostrou uma necessidade diferente, a documentação precisou acompanhar. Para mim, ela funcionou melhor como referência de trabalho do que como um plano que eu tinha de obedecer a qualquer custo.
E vai por mim, se você está desenvolvendo um produto de forma desorganizada, uma hora você você vai precisar de rastreabilidade - e vai dar ruim! Uma documentação sólida na era da IA é imprescindível ao desenvolvimento.
Aplicando desenvolvimento ágil: como transformei o plano em tasks e sprints
Com o escopo e as regras definidos, quebrei o desenvolvimento em sprints organizadas por dependência. A sequência começou pela base do projeto e pelos contratos, passou por banco e autenticação, depois pelos módulos da API, frontend, fluxos completos e preparação para produção. Assim, uma task de comentários, por exemplo, não aparecia antes de existir uma base para usuários, artigos e persistência.
Cada task tinha um objetivo, um checklist e um critério de conclusão verificável. Separei também o trabalho de implementação do de testes e documentação, sem tratar estes últimos como algo para resolver só no fim. As estimativas serviam para perceber quando uma task estava grande demais e precisava ser dividida, não como promessa de que tudo caberia exatamente naquele prazo.
O quadro de sprints ajudou a enxergar a ordem e o progresso, mas não transformou o projeto numa linha reta. Algumas decisões mudaram durante a construção, e certas tasks precisaram ser ajustadas. Ter o plano documentado facilitou isso: dava para entender o impacto de uma mudança sem depender apenas da memória do que eu tinha pensado semanas antes.
Como montei esse quadro?
De forma simples, integrei meu Notion ao Codex e apenas após definir todas as tasks e sprints de forma granular e montar um quadro provisório de documentação foi que pedi ao agente para consolidar esse quadro no Notion.
Esse quadro contém algumas colunas a mais do que se pode ver na captura de tela que fiz e também detalhes de descrição de uma task que considero importantes, como checks de definição de pronto e registro de entregas. Esse tópico (Kanban no Notion) merece um artigo apenas pra ele e que farei futuramente.
O que ficou desse projeto
Construir o Vavito Archives me fez lidar com problemas que não aparecem quando a gente pensa apenas em “fazer um blog”: versões pendentes de artigos, permissão administrativa, arquivos distribuídos entre banco e Storage, emails que não podem ser disparados duas vezes e mudanças que precisam passar por testes antes de chegar à produção.
Essas decisões não são visíveis para quem abre um artigo. E tudo bem. Para quem está lendo, o resultado esperado é bem mais simples: encontrar o conteúdo, conseguir lê-lo com conforto e usar as funcionalidades sem precisar entender o que acontece por trás.