Use Cases (Atualizado: 02/06/2026)

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.

Monorepo prático com Claude Code e pnpm workspace

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.

#claude-code #pnpm #workspace #monorepo #typescript #ci
Grátis

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.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.