O projeto começa com um tsconfig.json na raiz. Depois surgem uma biblioteca
compartilhada, uma API, um app web e testes que não deveriam entrar no mesmo
build. O compilador continua vendo uma pasta grande, o editor abre arquivos de
projetos diferentes e o CI roda um comando que não representa a estrutura real.
Project References resolvem essa fronteira. Você transforma cada parte em um
projeto TypeScript compilável, declara as dependências no tsconfig.json e usa
tsc -b para descobrir a ordem. O recurso não é um substituto para npm
workspaces nem uma justificativa para reorganizar qualquer repositório pequeno.
Este guia começa com um monorepo mínimo e termina com os critérios para decidir se a configuração vale a manutenção. O código é ilustrativo, não foi executado em um projeto de produção. Os comandos de verificação mostram como testar o mesmo arranjo na sua máquina.
Resposta curta
- Use Project References quando seu repositório tiver projetos que precisam de fronteiras, builds separados ou uma ordem de dependências explícita.
- Ative
compositenos projetos referenciados, gere declarações e mantenha umtsconfigde solução comfiles: [].- Rode
tsc -bna raiz.tscsem--buildnão executa o grafo de referências da mesma forma.- Se você tem poucos arquivos e nenhum limite real entre projetos, vários
tsconfigpodem adicionar mais trabalho que valor.
O que você precisa antes de começar?
Você precisa de Node.js em uma versão LTS mantida, npm, um terminal e
familiaridade básica com package.json e tsconfig.json. O exemplo usa npm
workspaces, mas a ideia do grafo também vale para pnpm, Yarn ou diretórios que
não são publicados como pacotes. Instale o TypeScript no workspace antes de
executar o script:
npm install --save-dev typescript @types/node
O objetivo é terminar com dois projetos compiláveis, um diretório dist por
projeto e uma configuração de solução que você consegue executar a partir da
raiz. Se o seu repositório usa um bundler, mantenha o comando de typecheck
separado do comando que empacota JavaScript.
Quando Project References valem a configuração?
O TypeScript introduziu Project References na versão 3.0 para dividir programas
em partes menores e trabalhar com o modo --build (TypeScript, “Project
References”, consultado em 24/08/2026).
O recurso vale a configuração quando a divisão representa uma dependência real,
não apenas uma preferência por pastas menores.
Procure estes sinais:
- uma biblioteca precisa ser compilada antes de uma API que a consome;
- testes e código de produção têm entradas, saídas ou opções diferentes;
- o editor e o CI precisam enxergar os mesmos limites;
- uma mudança em um pacote não deveria reprocessar todos os outros;
- a equipe quer tratar declarações
.d.tscomo a fronteira pública de um projeto.
Não leia a promessa de “build mais rápido” como um benchmark. A documentação oficial descreve o mecanismo de verificar quais projetos estão atualizados e compilar os que precisam, mas o ganho depende da forma do grafo, do bundler e da quantidade de código. Meça o seu repositório depois de a configuração estar correta.
A pergunta mais útil não é “quantos pacotes existem?”. É “qual projeto deveria
ser capaz de compilar sem abrir o código interno do outro?”. Se a resposta for
clara para pelo menos uma fronteira, Project References pode expressar uma
decisão de arquitetura. Se ninguém consegue nomear a fronteira, o problema ainda
é de organização, não de tsconfig.
O que npm workspaces resolve e o que não resolve?
O npm define workspaces como recursos para gerenciar vários pacotes locais a
partir de um pacote raiz. O npm install cria os links locais sem exigir
npm link manual (npm, “Workspaces”, consultado em 24/08/2026).
Isso resolve a instalação e a resolução de pacotes do workspace. Não descreve a
ordem de compilação do TypeScript.
Pense nas responsabilidades como duas camadas:
| Camada | Pergunta | Recurso |
|---|---|---|
| Pacotes | Como @acme/core aparece no node_modules local? |
npm workspaces |
| Compilação | O que precisa ser construído antes de apps/api? |
Project References |
| Tipos | Qual saída representa a fronteira de core? |
declaration e .d.ts |
| Orquestração | Qual comando descobre a ordem e pula o que está atualizado? | tsc -b |
Um workspace pode linkar core para api e ainda deixar o compilador tratando
os dois como uma única lista de arquivos. O inverso também é possível: Project
References podem organizar diretórios que não usam npm workspaces. Usar os dois
é comum porque um resolve a relação entre pacotes e o outro resolve a relação
entre projetos TypeScript.
Se o artigo sobre como criar um servidor MCP em TypeScript para agentes de código é o projeto que você está começando, uma única configuração provavelmente basta. Quando o servidor passa a compartilhar código com outro pacote, a pergunta muda para a fronteira de compilação.
Como montar um monorepo TypeScript mínimo?
Comece com uma biblioteca e uma aplicação. Essa forma pequena deixa o grafo visível e evita introduzir React, um bundler ou um gerenciador de monorepo antes de entender o compilador.
.
├── package.json
├── tsconfig.json
├── packages/
│ └── core/
│ ├── package.json
│ ├── tsconfig.json
│ └── src/index.ts
└── apps/
└── api/
├── package.json
├── tsconfig.json
└── src/index.ts
No package.json da raiz, declare os workspaces e os scripts que deixam o
comando de build explícito:
{
"name": "acme-workspace",
"private": true,
"workspaces": ["packages/*", "apps/*"],
"scripts": {
"build": "tsc -b tsconfig.json",
"build:verbose": "tsc -b tsconfig.json --verbose"
}
}
No pacote core, configure a saída que o consumidor vai ler:
{
"name": "@acme/core",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts"
}
O package.json da API pode depender do nome do workspace:
{
"name": "@acme/api",
"version": "1.0.0",
"dependencies": {
"@acme/core": "1.0.0"
}
}
Crie também uma função pequena em packages/core/src/index.ts para que a API
tenha uma superfície pública concreta:
export function formatServiceName(service: string): string {
return service.toUpperCase();
}
O npm documenta que uma dependência entre workspaces é vinculada localmente
quando o pacote está definido na configuração do workspace (npm, “Adding
dependencies to a workspace”, consultado em 24/08/2026).
O link local torna o pacote encontrável. A referência no tsconfig ainda será
necessária para o tsc -b conhecer a ordem.
Como declarar as referências e executar tsc -b?
Um projeto referenciado precisa de composite: true, e a raiz pode funcionar
como uma solução sem arquivos próprios. A documentação do TypeScript também
recomenda uma configuração com files: [] que aponta para os projetos folha
(TypeScript, “Project References”, consultado em 24/08/2026).
Crie primeiro a configuração de solução:
{
"files": [],
"references": [
{ "path": "./packages/core" },
{ "path": "./apps/api" }
]
}
O core precisa declarar sua própria entrada, saída e declarações:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"composite": true,
"declaration": true,
"declarationMap": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src/**/*.ts"]
}
No apps/api/tsconfig.json, repita as opções que precisam ser consistentes e
adicione a dependência de projeto:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"composite": true,
"declaration": true,
"declarationMap": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src/**/*.ts"],
"references": [
{ "path": "../../packages/core" }
]
}
O src/index.ts da API pode importar o pacote pelo nome:
import { formatServiceName } from "@acme/core";
console.log(formatServiceName("billing"));
Depois de instalar as dependências e garantir que o TypeScript está disponível, rode:
npm install
npx tsc -b tsconfig.json --verbose
O modo --build encontra projetos referenciados, verifica o que está atualizado
e constrói as dependências na ordem correta. O comando também pode receber o
caminho de um projeto específico, como npx tsc -b apps/api.
O exemplo é intencionalmente pequeno. Em um projeto real, centralize as opções
com extends, mas deixe rootDir, outDir e os arquivos incluídos coerentes em
cada pacote. Um tsconfig.base.json compartilhado não deve esconder a direção
das dependências.
O que composite, declaration e declarationMap mudam?
composite impõe restrições que ajudam o compilador a descobrir rapidamente se
um projeto já foi construído. A referência do TypeScript explica que os arquivos
de implementação precisam estar cobertos por include ou files, o rootDir
tem um comportamento padrão diferente e declaration é ativado para o projeto
referenciado (TypeScript, “Project References”, consultado em 24/08/2026).
Essas opções não são decoração. Elas tornam a saída do projeto parte do contrato:
compositediz que o projeto pode participar de um grafo de build;declarationproduz.d.tspara o consumidor enxergar a superfície tipada;declarationMapliga a declaração ao código-fonte para recursos como Go to Definition;outDirsepara a saída de cada projeto e evita colisões;tsBuildInfoFile, gerado por configurações incrementais, registra informação para builds seguintes.
O TSConfig Reference sobre declarationMap
recomenda considerar essa opção quando você usa Project References. Ainda assim,
ela não corrige um grafo errado. Se api referencia core, mas o código importa
arquivos internos por um alias que ignora a fronteira do pacote, a declaração
produzida não vai resolver o problema arquitetural.
Há um custo prático. Depois de clonar o repositório, os .d.ts esperados podem
ainda não existir. A própria documentação alerta que você pode precisar compilar
o projeto ou manter certas saídas versionadas para o editor navegar sem erros
falsos. Decida isso como parte do fluxo de desenvolvimento, não depois do
primeiro erro no computador de outra pessoa.
Não há um teste de produção neste artigo para escolher entre versionar dist e
gerá-lo no clone. Essa decisão depende do bundler, do ambiente e da política do
repositório. O ponto verificável é outro: o clone precisa conseguir gerar as
declarações antes de uma ferramenta que depende delas tentar resolver o pacote.
Como verificar se o grafo está correto?
Uma configuração só está pronta quando o comando de build prova a relação que o arquivo declara. Rode a construção detalhada e leia quais projetos foram considerados atualizados, reconstruídos ou ignorados:
npx tsc -b tsconfig.json --verbose --pretty false
Para simular a decisão sem escrever as saídas, use --dry quando a versão do
TypeScript instalada oferecer essa opção:
npx tsc -b tsconfig.json --dry --verbose
Depois da primeira execução, verifique os sinais que importam:
packages/core/dist/index.d.tsexiste e representa os exports públicos.apps/apicompila sem importar arquivos depackages/core/srcpor caminho relativo.- Alterar apenas
corefaz o build identificarapicomo dependente quando a saída pública muda. - Remover uma entrada de
referencesproduz um erro que explica a dependência ausente. - O mesmo script da raiz é usado no CI, em vez de um
tsc --noEmitque carrega apenas um projeto.
Se o pacote usa um bundler, separe as funções. O bundler pode produzir JavaScript
para a aplicação, enquanto tsc -b valida os limites e as declarações. Não
apresente um build do bundler como prova de que o grafo de Project References
está correto.
Para uma biblioteca consumida por outros pacotes, também vale revisar como você
publica tipos. O artigo sobre servir definições TypeScript para o Eden Treaty
trata de uma fronteira de pacote publicada. Aqui o foco é a fronteira interna,
mas os dois problemas se encontram quando o .d.ts local vira parte do contrato
que outro consumidor importa.
Por que o editor e o CI podem discordar?
O editor pode abrir um arquivo usando o tsconfig mais próximo, enquanto o CI
executa um comando na raiz. Project References ajudam a tornar o grafo explícito,
mas não fazem todas as ferramentas obedecerem ao mesmo programa. A documentação
do TypeScript alerta que os projetos dependentes usam declarações construídas e
que o tsc só constrói dependências automaticamente quando recebe --build
(TypeScript, “Caveats for Project References”, consultado em 24/08/2026).
Os sintomas mais comuns são:
- o editor navega para um tipo, mas
tsc --noEmitnão encontra o pacote; - a aplicação funciona depois de um build manual, mas falha em um clone limpo;
- o lint de tipos lê fontes enquanto o compilador lê
.d.tsproduzidos; - um alias de
pathsaponta parasrce ignora o pacote que o build publica; - o projeto foi referenciado, mas não tem
composite,includeououtDircompatível.
O issue sobre tipos que viram any em pacotes com Project References
é um bom registro de diagnóstico para esse tipo de atrito. Ele não é uma regra
para todos os projetos. Use-o para lembrar que editor, linter, workspace e
compilador podem resolver fronteiras de maneira diferente.
Quando uma ferramenta não entende o grafo, escolha uma estratégia explícita: configure essa ferramenta para ler os projetos corretos, rode o build antes da análise que depende de declarações ou mantenha uma configuração de typecheck separada. Não elimine as referências apenas para fazer um comando isolado passar sem entender o que ele estava verificando.
Quando não usar Project References?
Não use Project References apenas porque o repositório tem duas pastas. Se existe
uma aplicação pequena, um único processo de build e nenhuma fronteira que a
equipe queira proteger, um tsconfig.json simples pode ser mais legível.
Adie a adoção quando:
- o bundler ainda não sabe consumir as saídas ou os aliases do novo arranjo;
- os pacotes não têm uma relação de dependência que alguém consiga explicar;
- o time não tem um comando de clone limpo que gere as declarações necessárias;
- a única motivação é copiar uma configuração de um monorepo muito maior;
- o problema real é um módulo mal organizado, não a duração ou a abrangência do typecheck.
Use Project References quando a fronteira for útil mesmo sem o ganho de tempo. Uma biblioteca que não deveria importar a aplicação, um conjunto de testes com configuração própria ou um pacote publicado internamente já pode justificar o grafo. O build incremental é uma consequência possível, não a única razão.
Se a arquitetura ainda estiver confusa, comece por separar serviços e responsabilidades no código. O guia sobre organizar serviços em uma arquitetura MSC com TypeScript trata dessa fronteira em outro nível. Project References tornam uma divisão existente verificável; não inventam uma boa divisão sozinhas.
Perguntas frequentes
Project References funcionam sem um monorepo?
Sim. A documentação do TypeScript descreve referências entre diretórios que têm
seus próprios tsconfig.json. Um monorepo com workspaces é uma combinação comum,
mas não é requisito. O que importa é haver projetos TypeScript separados e uma
dependência que você quer declarar.
Posso usar tsc -p em vez de tsc -b?
Pode usar tsc -p para compilar um projeto isolado, mas esse comando não é o
orquestrador do grafo. Para construir dependências na ordem e reaproveitar o
estado conhecido pelo build, use tsc -b com a configuração de solução. Compare
os comandos com --verbose antes de decidir o script do CI.
Preciso versionar os arquivos .d.ts e dist?
Não existe uma resposta universal. O TypeScript alerta que os projetos dependentes precisam das declarações para navegação e compilação. Você pode gerá-las após o clone, versioná-las ou fazer o ambiente de desenvolvimento cuidar desse passo. Escolha uma política e faça o CI detectar um clone sem as saídas.
Project References substituem Turborepo, Nx ou outro build tool?
Não. Eles descrevem o grafo que o TypeScript entende. Um build tool pode cuidar de
cache, tarefas, bundling, testes e execução entre pacotes. Comece pelo recurso
nativo quando o problema for a fronteira de compilação. Adicione outra camada
quando existir uma necessidade que tsc -b não cobre.
Conclusão
Project References são uma forma de dizer ao TypeScript que o repositório tem
projetos, não apenas pastas. Workspaces ligam os pacotes locais. references
declara quem depende de quem. composite e declarações tornam a fronteira
compilável. tsc -b prova a ordem e encontra o que precisa ser reconstruído.
Comece com duas partes e um grafo pequeno. Rode o build em um clone limpo,
confira os .d.ts, alinhe o editor e o CI e só então divida mais projetos. Se a
fronteira não explica uma decisão real do código, a configuração ainda não
resolveu o problema que motivou a busca.
Fontes consultadas
- TypeScript, “Project References”, consultado em 24/08/2026.
- TypeScript, “TSConfig Reference”, consultado em 24/08/2026.
- npm, “Workspaces”, consultado em 24/08/2026.
- TypeScript, “Modules: Reference”, consultado em 24/08/2026.
- Microsoft TypeScript, “Types resolving to
anyacross packages in monorepo using project references”, consultado em 24/08/2026.