Monorepo pratique avec Claude Code et pnpm workspace
Construisez un petit monorepo avec Claude Code et pnpm workspace: dépendances, filter, CI et pièges courants.
pnpm workspace évite de copier le même code partout
Je suis Masa, l’opérateur de claudecode-lab.com.
Même un petit produit se divise vite en plusieurs morceaux: application publique, tableau d’administration, UI partagée, configuration, scripts de contenu, jobs email et outils de test. Le problème n’est pas d’avoir plusieurs dossiers. Le problème commence quand le même helper, la même URL publique ou le même schema de validation est copié dans trois endroits.
pnpm workspace permet de gérer plusieurs packages dans un seul dépôt Git. La documentation officielle pnpm Workspace explique qu’un workspace regroupe plusieurs projets et nécessite un fichier pnpm-workspace.yaml à la racine.
Claude Code est surtout utile comme relecteur. Il peut repérer un workspace:* manquant, une dépendance dans le mauvais sens, une CI qui reconstruit tout inutilement, ou un package partagé qui reçoit de la logique métier. Ce sont ces petites dérives qui rendent un monorepo coûteux.
Ce guide utilise pnpm 11.5.0. Pour le contexte général, consultez aussi la gestion de monorepo avec Claude Code et la gestion des dépendances.
Structure cible: quatre packages pour bien commencer
Inutile de créer une grande plateforme dès le départ. Deux apps et deux packages partagés suffisent souvent.
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
Trois usages sont immédiats. D’abord, packages/ui peut contenir de petits helpers de présentation utilisés par l’app web et l’admin. Ensuite, packages/config centralise les URL publiques, les noms de feature flags et les constantes. Enfin, vous pouvez ajouter plus tard packages/contracts pour partager des types d’API ou des schemas Zod entre frontend et backend.
Le piège consiste à créer packages/common pour tout ce qui ne trouve pas sa place. Un package partagé doit avoir le même sens pour tous ses consommateurs. Avec Claude Code, formulez la demande ainsi: “extrais seulement le helper UI dupliqué; laisse la logique de facturation dans l’application”.
Configuration minimale à copier
Créez d’abord pnpm-workspace.yaml. La page officielle pnpm-workspace.yaml montre comment inclure ou exclure des dossiers.
packages:
- "apps/*"
- "packages/*"
catalog:
typescript: ^5.8.3
Le package.json racine orchestre les commandes.
{
"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
}
}
Dans .npmrc, rendez la résolution locale explicite:
link-workspace-packages=false
save-workspace-protocol=rolling
shared-workspace-lockfile=true
strict-peer-dependencies=true
auto-install-peers=false
L’élément important est workspace:*. pnpm documente que le protocole workspace: refuse de résoudre une dépendance en dehors du workspace local. Cela évite de récupérer par erreur un package du registry portant le même nom.
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 dépend de config via le 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}`;
}
L’application web déclare seulement ce qu’elle utilise:
{
"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"));
Lancement:
corepack pnpm install
corepack pnpm --filter @acme/web build
corepack pnpm -r --sort --if-present build
Donnez d’abord les frontières à Claude Code
Claude Code propose un guide officiel pour les monorepos and large codebases. La leçon principale est de réduire le contexte inutile. Dans un pnpm workspace, un lancement depuis la racine doit être accompagné d’une portée claire.
Le CLAUDE.md racine reste global:
# 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.
Les règles locales appartiennent au dossier concerné. La documentation memory de Claude Code explique que CLAUDE.md fournit des instructions persistantes. Il faut donc éviter les longs textes génériques.
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.
"
Cette demande force une inspection avant toute modification.
filter rend le travail quotidien et la CI plus légers
pnpm Filtering restreint une commande à un sous-ensemble de packages.
pnpm --filter @acme/web build
pnpm --filter @acme/web... build
pnpm --filter ...@acme/ui test
pnpm --filter "...[origin/main]" --if-present test
Le piège est le sens des .... @acme/web... sélectionne web et ses dépendances. ...@acme/ui sélectionne ui et les packages qui dépendent de ui. Après un changement UI, exécuter seulement @acme/ui... peut oublier les tests web/admin.
Une CI ciblée peut ressembler à ceci:
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
Pièges fréquents et corrections concrètes
Premier piège: déclarer une dépendance interne comme une version semver normale.
{
"dependencies": {
"@acme/ui": "^0.1.0"
}
}
Dans le workspace, écrivez plutôt:
{
"dependencies": {
"@acme/ui": "workspace:*"
}
}
Deuxième piège: mettre de la logique métier dans un package partagé. packages/ui ne doit pas appeler d’API ni décider du plan de facturation. Troisième piège: les dépendances circulaires. Si packages/ui importe depuis apps/web, la frontière est cassée.
Je demande régulièrement cette vérification:
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.
"
Si vous publiez des packages, ajoutez Changesets:
pnpm add -Dw @changesets/cli
pnpm changeset init
pnpm changeset
pnpm changeset version
pnpm -r publish --access public
La documentation pnpm indique que le versioning de packages workspace s’appuie généralement sur Changesets ou Rush. Les applications apps/* restent normalement private: true.
Résumé et résultat vérifié
pnpm workspace n’est pas une couche lourde. C’est une base simple pour traiter l’UI, la configuration, les types et les tests partagés comme de vraies dépendances. Claude Code est utile pour revoir le graphe, limiter les changements et analyser les échecs de CI.
Ensuite, améliorez vos instructions avec CLAUDE.md best practices et stabilisez les vérifications avec Claude Code testing strategies. Pour un déploiement en équipe, utilisez la page Claude Code training.
J’ai vérifié l’exemple avec Windows, Node.js 22, Corepack et pnpm 11.5.0. En pratique, les deux erreurs les plus fréquentes sont l’oubli de workspace:* et le mauvais sens de filter. Faire afficher le package graph par Claude Code avant toute édition aide à repérer les abstractions inutiles et les dépendances circulaires.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Workflow Obsidian vers CLAUDE.md avec Claude Code
Transformer des notes Obsidian en notes CLAUDE.md concises pour reprendre les sessions sans réexpliquer.
Claude Code Revenue CTA Routing : relier articles, PDF, Gumroad et consultation
Un workflow Claude Code pour orienter les lecteurs vers PDF gratuit, Gumroad ou consultation selon l'intention.
Règles de handoff Claude Code en équipe: preuves, permissions, rollback et revenus
Un format concret pour transmettre un travail Claude Code avec preuves, permissions, rollback, PDF gratuit, Gumroad et consultation.