Monorepos e tooling frontend: Vite, Turborepo e builds que escalam
Conforme uma empresa cresce, o código se multiplica: o app web, o painel administrativo, o site de marketing, a biblioteca de componentes, os utilitários compartilhados. A pergunta que toda equipe acaba enfrentando é como organizar tudo isso. Um repositório para cada coisa, com o custo de sincronizar mudanças entre eles? Ou um único repositório que abriga tudo? Essa segunda opção, o monorepo, virou padrão em muitos times de frontend justamente porque resolve o problema de compartilhar código sem a dor de coordenar dezenas de repositórios.
Mas monorepo não é uma bala de prata, e adotá-lo sem o tooling certo troca um conjunto de problemas por outro. Este guia explica o que é um monorepo de verdade, como workspaces, cache de build e CI seletivo o tornam rápido, como versionar pacotes internos e, com igual importância, quando não vale a pena usá-lo.
O que é (e o que não é) um monorepo
Um monorepo é um único repositório que contém múltiplos projetos ou pacotes relacionados, com um histórico de versionamento compartilhado. Vale desfazer duas confusões comuns:
- Não é uma "pasta gigante de código bagunçado". Um monorepo bem estruturado tem limites claros entre pacotes, cada um com sua responsabilidade e suas dependências explícitas.
- Não é o mesmo que monolito. Monolito é sobre arquitetura de execução (um único artefato que roda junto). Monorepo é sobre organização de código. Dá para ter um monorepo que produz vários serviços independentes, e um monolito espalhado por vários repositórios.
O valor central é o código compartilhado sem fricção: uma biblioteca de componentes ou um pacote de utilitários vive ao lado de quem o consome, e uma mudança atravessa tudo em um único commit, com uma única revisão.
Workspaces: a base da organização
A peça fundamental é o conceito de workspaces, suportado pelos gerenciadores de pacote modernos. Um workspace declara que aquele repositório contém vários pacotes e permite que eles se refiram uns aos outros como se fossem dependências normais, sem publicar nada.
Uma estrutura típica separa aplicações de bibliotecas compartilhadas:
meu-monorepo/
├── apps/
│ ├── web/ # aplicação principal
│ └── admin/ # painel administrativo
├── packages/
│ ├── ui/ # biblioteca de componentes
│ ├── config/ # configs compartilhadas (lint, tsconfig)
│ └── utils/ # utilitários comuns
└── package.json # define os workspaces
Com isso, o apps/web importa @empresa/ui diretamente, e qualquer alteração no pacote de UI já reflete no app, sem passos de publicação. As dependências são instaladas de forma unificada, evitando duplicação e conflitos de versão entre os projetos.
O problema que o tooling resolve: velocidade
Aqui está o ponto que separa um monorepo bom de um insuportável. Sem ferramentas adequadas, um monorepo grande fica lento: qualquer mudança dispara a reconstrução e o teste de tudo, mesmo do que não foi afetado. É aí que entram orquestradores de build como o Turborepo (e alternativas no mesmo espírito), que introduzem dois superpoderes.
Cache de build
O primeiro é o cache. A ferramenta entende o grafo de dependências entre os pacotes e guarda o resultado de cada tarefa (build, teste, lint). Se nada mudou na entrada de uma tarefa, ela não roda de novo: o resultado sai do cache, instantaneamente. Rodar o build duas vezes seguidas, sem mudanças, deixa de custar minutos e passa a custar segundos.
Melhor ainda, o cache pode ser remoto e compartilhado pelo time e pela CI. Se um colega (ou o pipeline) já construiu determinado pacote com aquele mesmo estado, a sua máquina reaproveita o resultado, sem reconstruir localmente. O trabalho é feito uma vez e reusado por todos.
CI seletivo (affected)
O segundo superpoder é rodar apenas o que foi afetado. Ao conhecer o grafo de dependências, a ferramenta calcula quais pacotes uma mudança realmente impacta e limita build e testes a eles. Alterar o site de marketing não precisa reconstruir e testar o painel administrativo. Em um monorepo grande, isso corta drasticamente o tempo de CI e devolve o feedback rápido que a mudança de repositório único ameaçava tirar.
Sobre as ferramentas de build de cada app, o Vite se tornou o padrão pela velocidade: servidor de desenvolvimento quase instantâneo e builds de produção otimizados. Em um monorepo, cada app pode ter seu Vite, enquanto o orquestrador coordena as tarefas entre todos os pacotes.
Versionamento de pacotes internos
Como lidar com versões quando os pacotes vivem juntos? Há duas filosofias:
| Abordagem | Como funciona | Melhor para |
|---|---|---|
| Versão fixa (lockstep) | Todos os pacotes sobem de versão juntos | Times que publicam tudo em conjunto |
| Versão independente | Cada pacote tem sua própria versão | Bibliotecas com consumidores externos |
Para pacotes puramente internos (consumidos só dentro do monorepo), muitas vezes nem é preciso versionar de forma tradicional: como tudo muda no mesmo commit, o apps/web sempre usa a versão mais recente do packages/ui. O versionamento formal, com changelog e publicação, ganha importância quando um pacote também é distribuído para fora, e aí valem os mesmos princípios de retrocompatibilidade de qualquer biblioteca pública: mudanças que quebram exigem uma nova versão maior e um aviso claro. Ferramentas de gestão de releases automatizam esse fluxo, gerando changelog a partir dos commits.
Quando NÃO usar um monorepo
Tão importante quanto saber adotar é saber recusar. Monorepo tem custos, e em vários cenários ele é a escolha errada:
- Projetos pequenos ou isolados. Um único app sem código compartilhado não ganha nada com a complexidade de um monorepo. Um repositório simples é melhor.
- Times totalmente independentes, com ciclos de release e stacks que não se cruzam. Se os projetos não compartilham código nem coordenação, forçá-los no mesmo repositório só adiciona atrito.
- Sem apetite para investir em tooling. Um monorepo grande exige cache e CI seletivo para não virar um gargalo. Sem esse investimento, a lentidão come todo o benefício.
- Necessidade de permissões muito granulares por projeto, algo que repositórios separados resolvem de forma mais natural.
A pergunta honesta é: existe código compartilhado suficiente e coordenação suficiente entre os projetos para justificar a estrutura? Se a resposta é não, repositórios separados são mais simples e igualmente válidos. Monorepo é uma resposta para um problema de escala e compartilhamento, não um objetivo em si.
Estrutura a serviço da velocidade
Um monorepo bem montado dá ao time o melhor de dois mundos: o compartilhamento de código sem a burocracia de coordenar muitos repositórios, e a velocidade de builds que só reconstroem o que mudou. A chave não é o repositório único em si, é o tooling que o torna rápido: workspaces organizando os pacotes, cache eliminando trabalho repetido e CI seletivo mantendo o feedback ágil mesmo com o código crescendo. Sem isso, o monorepo trava; com isso, ele escala.
Na ConstructorTI, ajudamos times a estruturar monorepos frontend que escalam de verdade: workspaces bem desenhados, orquestração de build com cache local e remoto, CI seletivo por pacotes afetados e uma estratégia de versionamento adequada ao seu contexto, além do julgamento honesto de quando um monorepo não é a resposta. Se os seus builds estão lentos, o código está duplicado entre projetos ou você está avaliando reorganizar os repositórios, fale com o nosso time de engenharia e saia da conversa com um plano claro de arquitetura e tooling.