Monorepo prático com Claude Code e pnpm workspace
Monte um monorepo pequeno com Claude Code e pnpm workspace: dependências, filter, CI e armadilhas comuns.
pnpm workspace evita que o produto vire uma coleção de cópias
Sou Masa, operador do claudecode-lab.com.
Mesmo um produto pequeno se divide rapidamente. Você começa com uma aplicação web, depois surge um painel admin, componentes compartilhados, configuração, scripts de conteúdo, jobs de email e utilitários de teste. O problema não é ter muitas pastas. O problema começa quando o mesmo helper, o mesmo nome de variável de ambiente ou o mesmo schema de validação aparece copiado em três lugares.
pnpm workspace permite gerenciar vários packages dentro de um único repositório Git. A documentação oficial de pnpm Workspace explica que um workspace une múltiplos projetos e precisa de um pnpm-workspace.yaml na raiz.
Claude Code entra melhor como revisor do que como gerador. Ele pode checar se falta workspace:*, se packages/* está importando de apps/*, se o CI roda tudo sem necessidade, ou se um pacote compartilhado está recebendo lógica de negócio demais. Esses detalhes são o que mantém um monorepo simples.
Este guia usa pnpm 11.5.0. Para ampliar a base, veja também gerenciamento de monorepo com Claude Code e gerenciamento de dependências.
Estrutura alvo: quatro packages são suficientes
Não comece com uma plataforma enorme. Dois apps e dois pacotes compartilhados dão um limite claro.
flowchart LR
web["apps/web\n@acme/web"] --> ui["packages/ui\n@acme/ui"]
web --> config["packages/config\n@acme/config"]
admin["apps/admin\n@acme/admin"] --> ui
admin --> config
acme-workspace/
apps/web/src/main.ts
apps/web/package.json
apps/admin/src/main.ts
apps/admin/package.json
packages/config/src/index.ts
packages/config/package.json
packages/ui/src/index.ts
packages/ui/package.json
pnpm-workspace.yaml
package.json
.npmrc
CLAUDE.md
Há pelo menos três casos de uso. Primeiro, packages/ui guarda helpers de apresentação e pequenas primitivas de UI usadas pelo app web e pelo admin. Segundo, packages/config centraliza URLs públicas, nomes de feature flags e constantes. Terceiro, você pode adicionar packages/contracts depois para compartilhar tipos de API ou schemas Zod entre frontend e backend.
O erro é criar packages/common e colocar tudo lá. Código compartilhado deve ter o mesmo significado para todos os consumidores. Ao pedir ajuda ao Claude Code, use uma instrução específica: “extraia apenas o helper de UI duplicado; mantenha a lógica de billing dentro do app”.
Configuração mínima para copiar
Comece com pnpm-workspace.yaml. A página oficial de pnpm-workspace.yaml mostra como incluir e excluir diretórios.
packages:
- "apps/*"
- "packages/*"
catalog:
typescript: ^5.8.3
O package.json da raiz só coordena comandos.
{
"name": "acme-workspace",
"private": true,
"packageManager": "pnpm@11.5.0",
"scripts": {
"check:web": "pnpm --filter @acme/web build",
"build": "pnpm -r --sort --if-present build",
"test": "pnpm -r --if-present test",
"changed:test": "pnpm --filter \"...[origin/main]\" --if-present test"
},
"devDependencies": {
"typescript": "catalog:"
}
}
tsconfig.base.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"skipLibCheck": true,
"noEmit": true
}
}
No .npmrc, deixe a resolução local explícita:
link-workspace-packages=false
save-workspace-protocol=rolling
shared-workspace-lockfile=true
strict-peer-dependencies=true
auto-install-peers=false
O ponto essencial é workspace:*. A documentação do pnpm diz que o protocolo workspace: não resolve fora do workspace local. Assim você não instala por engano um package do registry com o mesmo nome do seu package interno.
packages/config/package.json:
{
"name": "@acme/config",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts"
}
}
}
export const appConfig = {
productName: "Acme Workspace",
supportEmail: "support@example.com",
publicSiteUrl: "https://example.com"
} as const;
packages/ui depende de config via workspace:
{
"name": "@acme/ui",
"version": "0.1.0",
"private": true,
"type": "module",
"dependencies": {
"@acme/config": "workspace:*"
},
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts"
}
}
}
import { appConfig } from "@acme/config";
export function renderPrimaryButton(label: string): string {
return `[${appConfig.productName}] ${label}`;
}
O app declara só as dependências que usa:
{
"name": "@acme/web",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc -p tsconfig.json"
},
"dependencies": {
"@acme/config": "workspace:*",
"@acme/ui": "workspace:*"
}
}
apps/web/tsconfig.json:
{
"extends": "../../tsconfig.base.json",
"include": ["src"]
}
import { appConfig } from "@acme/config";
import { renderPrimaryButton } from "@acme/ui";
console.log(appConfig.publicSiteUrl);
console.log(renderPrimaryButton("Start trial"));
Execute:
corepack pnpm install
corepack pnpm --filter @acme/web build
corepack pnpm -r --sort --if-present build
Ensine os limites para o Claude Code
Claude Code tem um guia oficial para monorepos and large codebases. A ideia principal é reduzir leitura irrelevante. Em um pnpm workspace, iniciar pela raiz é possível, mas a tarefa precisa dizer quais packages estão no escopo.
O CLAUDE.md da raiz deve conter apenas regras globais:
# Acme Workspace
This repository is a pnpm workspace.
Packages:
- apps/web: customer-facing TypeScript app
- apps/admin: internal admin app
- packages/ui: shared UI helpers
- packages/config: shared runtime constants
Rules:
- Use pnpm, not npm or yarn.
- Add internal dependencies with workspace:*.
- Run focused commands with pnpm --filter before full workspace commands.
- Do not move business logic into packages/ui.
Regras locais ficam no diretório do package. A documentação de memory do Claude Code explica que CLAUDE.md fornece instruções persistentes. Por isso, prefira regras curtas e específicas.
claude -p "
Inspect this pnpm workspace. Do not edit files yet.
List the package graph, scripts, and risky dependency directions.
Then propose the smallest change needed to share UI helpers between apps/web and apps/admin.
"
Esse prompt obriga inspeção antes da edição e evita refactors grandes demais.
filter deixa desenvolvimento e CI mais leves
pnpm Filtering restringe um comando a um conjunto de packages.
pnpm --filter @acme/web build
pnpm --filter @acme/web... build
pnpm --filter ...@acme/ui test
pnpm --filter "...[origin/main]" --if-present test
O erro comum é a direção do .... @acme/web... seleciona web e suas dependências. ...@acme/ui seleciona ui e quem depende de ui. Depois de alterar UI, rodar só @acme/ui... pode pular testes do web/admin.
Um CI focado pode ficar assim:
name: workspace-check
on:
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: corepack enable
- run: corepack prepare pnpm@11.5.0 --activate
- run: pnpm install --frozen-lockfile
- run: pnpm --filter "...[origin/main]" --if-present test
- run: pnpm --filter "...[origin/main]" --if-present build
Armadilhas comuns e correções
A primeira armadilha é declarar dependência interna como semver normal:
{
"dependencies": {
"@acme/ui": "^0.1.0"
}
}
No workspace, use:
{
"dependencies": {
"@acme/ui": "workspace:*"
}
}
A segunda armadilha é colocar lógica específica do app em package compartilhado. packages/ui não deve chamar APIs nem decidir plano de billing. A terceira é criar dependência circular. Se packages/ui importa de apps/web, o desenho está invertido.
Peça esta auditoria ao Claude Code:
claude -p "
Check this workspace for circular dependencies and misplaced imports.
Focus on packages/* importing from apps/*, duplicated config values,
and dependencies that should be workspace:*.
Return findings with file paths and minimal fixes.
"
Se precisar publicar packages, adicione Changesets:
pnpm add -Dw @changesets/cli
pnpm changeset init
pnpm changeset
pnpm changeset version
pnpm -r publish --access public
A documentação do pnpm aponta Changesets ou Rush para versionamento de packages em workspace. Apps em apps/* normalmente continuam com private: true.
Resumo e resultado prático
pnpm workspace não é uma camada pesada. É uma base pequena para tratar UI, configuração, tipos e testes compartilhados como dependências explícitas. Claude Code ajuda quando revisa o grafo, propõe a menor mudança e reduz falhas de CI.
Como próximos passos, veja CLAUDE.md best practices e Claude Code testing strategies. Para adoção em equipe, use a página de Claude Code training.
Verifiquei o exemplo com Windows, Node.js 22, Corepack e pnpm 11.5.0. Na prática, os erros mais comuns são esquecer workspace:* e inverter o sentido do filter. Fazer o Claude Code mostrar o package graph antes de editar ajuda a encontrar abstrações desnecessárias e dependências circulares cedo.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Workflow Obsidian para CLAUDE.md com Claude Code
Transforme notas de trabalho do Obsidian em notas operacionais CLAUDE.md para preservar contexto.
Claude Code Revenue CTA Routing: artigos para PDF, Gumroad e consultoria
Um fluxo com Claude Code para levar leitores ao PDF grátis, Gumroad ou consultoria conforme intenção.
Regras de handoff para equipes com Claude Code: evidências, permissões, rollback e receita
Formato prático para entregar trabalho do Claude Code com prova, permissões, rollback, PDF grátis, Gumroad e consultoria.