Quando um repositório só não basta.
Março de 2026
O ponto de virada
Antes desse portfólio, eu já tinha feito outros. Vários, na verdade. Designs diferentes, repositórios diferentes, mas sempre a mesma estrutura: um projeto Next.js com tudo dentro.
E funcionava. Até eu decidir que esse portfólio ia ser diferente.
Dessa vez não era só uma vitrine de projetos. Eu queria um blog, um formulário de orçamento que coletasse tudo de uma vez, uma página de projetos com cases completos. E se eu tinha tudo isso no site público, eu precisava de algum lugar para gerenciar.
Orçamentos chegando, mensagens na inbox, posts do blog pra revisar. Ia colocar tudo isso dentro do mesmo repositório do portfólio?
O problema de tudo junto
Eu já sabia como ia terminar. A pasta src ia crescer, os arquivos iam se multiplicar, e em algum momento eu ia perder tempo procurando onde ficava cada coisa em vez de estar programando.
Mas não era só organização. Tinha coisas práticas que me incomodavam:
‒ Performance misturada — o portfólio público precisa ser leve e rápido. O admin lida com banco de dados, integrações e lógica pesada. São perfis completamente diferentes convivendo no mesmo build
‒ Middleware desnecessário — autenticação, proteção de rotas, verificações de sessão. O portfólio não precisa de nada disso. Mas com tudo junto, o middleware roda pra todas as rotas
‒ Deploy acoplado — uma mudança no admin forçava rebuild do portfólio inteiro. E vice-versa. Tempo de build crescendo sem motivo
‒ Personalização limitada — cada app tem sua identidade. O portfólio tem uma linguagem visual, o admin tem outra. Com tudo junto, qualquer customização vira gambiarra de condicionais
A decisão ficou clara: separar.
A estrutura que nasceu
Criei o monorepo com Turborepo e pnpm workspaces. Hoje ele tem dois apps e vários pacotes compartilhados:
‒ apps/web — o portfólio público. Blog, projetos, orçamento, contato. Roda o mais leve possível, focado em performance e SEO
‒ apps/admin — painel administrativo. Gerencia orçamentos, mensagens, posts do blog, publicação nas redes sociais. Aqui vive a lógica pesada
E os pacotes compartilhados são o que conecta tudo:
‒ packages/ui — componentes de interface reutilizados nos dois apps
‒ packages/database — schema do Drizzle, conexão com o Neon (PostgreSQL), tudo tipado de ponta a ponta
‒ packages/sanity — client, queries GROQ, tipos e utilitários do CMS
‒ packages/email — templates de email com React Email (confirmação, resposta, newsletter)
O que me surpreendeu
A primeira coisa que me surpreendeu foi o pnpm dev. Um comando na raiz e os dois apps sobem ao mesmo tempo, cada um na sua porta. Parece simples, mas quando você vem de abrir dois terminais e rodar dois projetos separados, é uma diferença enorme no dia a dia.
A segunda foi o compartilhamento real. Eu altero um componente no packages/ui e ele atualiza nos dois apps instantaneamente. Mudo o schema do banco no packages/database e o admin e o web já têm acesso ao tipo atualizado. Sem publicar pacote, sem copiar arquivo. Uma source of truth.
E a terceira foi perceber como cada app respira melhor separado. O portfólio builda em segundos porque não carrega a complexidade do admin. O admin pode ter suas próprias dependências pesadas sem afetar o tempo de carregamento do site público. Cada um faz o seu, sem atrapalhar o outro.
O que custou
Nem tudo foi suave. Configurar os imports compartilhados entre os pacotes deu trabalho. Não porque era conceitualmente difícil, mas porque os erros que apareciam eram confusos — problemas de resolução de módulo, TypeScript reclamando de caminhos, exports que não eram reconhecidos.
Nenhum desses erros era impossível de resolver. Mas cada um demorava mais do que deveria pra diagnosticar, porque as mensagens de erro raramente apontavam o problema real. Você achava que era o import, mas era o exports do package.json. Achava que era o TypeScript, mas era a ordem dos campos no tsconfig.
"A parte mais difícil de um monorepo não é a arquitetura. É convencer o TypeScript de que os seus pacotes existem."
O que eu faria diferente
Se eu começasse hoje do zero, faria a mesma escolha. Mas teria definido a estrutura dos pacotes compartilhados desde o primeiro dia, em vez de ir extraindo conforme a necessidade. Quando você extrai depois, acaba com imports quebrados e refatorações que poderiam ter sido evitadas.
Também teria investido mais tempo no início entendendo como o package.json de cada pacote precisa ser configurado. A maioria dos erros que tive vieram de exports mal definidos. Uma hora lendo a documentação no começo teria economizado horas de debug depois.
Esse é só o começo
O monorepo abriu portas que eu não tinha pensado quando comecei. A possibilidade de adicionar novos apps sem reconfigurar tudo, de ter uma base de código que cresce de forma organizada, de compartilhar sem duplicar. Tem coisa nova vindo por aí.
Pretendo documentar cada adição aqui no blog — o que fiz, por que fiz e o que aprendi.
Se quiser acompanhar essa jornada, me encontra no LinkedIn. E se tiver um projeto em mente, dá uma olhada no formulário de orçamento — é exatamente pra isso que ele existe.
"Organização não é sobre ter menos código. É sobre saber onde cada coisa vive."