TL;DR: Most teams building multiple React applications make the same expensive mistake: they start each app as a separate repository. Authentication gets rebuilt.
The design system diverges. Payment logic gets copied and then patched inconsistently. A monorepo; one shared codebase with multiple React apps on top, solves all of this at the root.
With Claude Code managing the implementation, you can build this foundation properly from the start, not retrofit it after the pain has compounded.
The separate repo trap
Picture the typical trajectory. You build your first React app. It works. You need a second — an admin portal, a client-facing dashboard, a customer portal. You clone the repo, strip out the specific logic, and start again. It feels efficient on day one.
Six months later you are maintaining three separate codebases. A security patch in your authentication layer needs applying three times. Your design tokens have drifted. Your Stripe webhook handler in app two has a bug that was fixed in app one but never ported across. A dependency update needs coordinating across three separate package.json files and three separate PRs.
| Separate repos | Shared monorepo |
|---|---|
| Auth logic duplicated and diverges | Auth fixed once, inherited by all apps |
| Security fixes applied repo-by-repo | Security hardening applies everywhere |
| Shared UI components drift | One design system, one source of truth |
| Stripe logic inconsistently patched | Stripe integration shared and maintained |
| New app = rebuild everything | New app scaffolds from proven foundation |
| Claude Code loses context crossing repos | Claude navigates the whole codebase in one session |
What a production React monorepo looks like
monorepo/ ├── apps/ │ ├── customer-portal/ # React app — customer-facing │ ├── admin-dashboard/ # React app — internal ops │ ├── client-portal/ # React app — per-client white-label │ └── marketing-site/ # Next.js — public marketing ├── packages/ │ ├── ui/ # Shared design system + components │ ├── auth/ # Shared authentication layer │ ├── payments/ # Shared Stripe integration │ ├── api-client/ # Shared HTTP clients + error handling │ ├── utils/ # Shared utility functions │ └── config/ # Shared ESLint, TypeScript, Tailwind config ├── CLAUDE.md # Monorepo-level rules for Claude Code ├── turbo.json # Turborepo task orchestration └── package.json # Root workspace config
Fixing a bug in packages/auth fixes it for every app simultaneously. Adding a component to packages/ui makes it available everywhere. Updating your Stripe webhook handler propagates to all apps in the next build.
Choosing your tooling: Turborepo vs Nx
Turborepo (recommended for most teams) — optimised specifically for JavaScript and TypeScript monorepos, integrates cleanly with pnpm workspaces, and provides intelligent build caching: only rebuilds packages that have actually changed. The learning curve is gentle, the tooling stays out of your way, and Claude Code works with it naturally.
Nx — better for larger teams, advanced dependency graph control, and mixed-technology monorepos. Higher setup cost, pays off at scale.
Recommended stack: pnpm workspaces + Turborepo + TypeScript path aliases. This is the combination Claude Code handles most fluently.
Package 1: Shared authentication layer
Authentication is the highest-risk place for inconsistency. Build it once in packages/auth — JWT handling and refresh token rotation, session persistence, role-based access control (RBAC), protected route wrappers, auth context providers. Every app imports from @yourmonorepo/auth. When you patch a vulnerability, you patch it once and it covers every app.
Claude Code prompt:
Build a shared auth package for our monorepo at packages/auth/. Requirements: - JWT access tokens (15min expiry) + refresh tokens (7 day, httpOnly cookie) - Token refresh with retry and race condition protection - React AuthContext + useAuth hook - ProtectedRoute component with role-based redirect - RBAC: roles array on the JWT payload, hasRole() and hasAnyRole() helpers - TypeScript throughout - No direct app-level dependencies — peer deps only (React, React Router) Write unit tests for token refresh and RBAC logic. Follow CLAUDE.md conventions for this monorepo.
Package 2: Shared Stripe integration
Build your Stripe integration once in packages/payments. It should own: Stripe checkout session creation, subscription management, webhook handlers with idempotency keys, subscription status hooks for the frontend, and plan entitlement checking.
⚠️ Webhook security is non-negotiable. Always verify the Stripe-Signature header against your webhook secret before processing any event. A webhook endpoint that skips signature verification is a significant security risk.
Claude Code prompt:
Build a shared Stripe payments package at packages/payments/. Requirements: - Checkout session creation (one-time and subscription) - Subscription management: create, upgrade, downgrade, cancel - Webhook handler with: - Stripe signature verification (MUST reject invalid signatures) - Idempotency key handling (prevent duplicate processing) - Event handlers for: checkout.session.completed, customer.subscription.updated, invoice.payment_failed - useSubscription() React hook (current plan, status, next billing date) - Plan entitlement helper: hasFeature(featureKey) All webhook processing failures must throw — never silently fail. Write tests for signature verification and idempotency logic.
Package 3: Shared design system
A shared packages/ui design system ensures every app looks and behaves identically — buttons, form inputs, modals, data tables, loading states, error boundaries — without duplicated styling logic. Build it on Tailwind CSS with a shared config and shadcn/ui as your component foundation.
Design tokens (colours, spacing, typography) live in the shared Tailwind config and propagate to every app automatically.
Package 4: Shared API client
Every app making API calls benefits from a shared HTTP client that handles authentication headers automatically, normalises errors into a consistent shape, manages token refresh transparently, and provides typed request/response interfaces. Fix a timeout handling bug once and it propagates everywhere.
Your CLAUDE.md for a monorepo
# CLAUDE.md — Monorepo Root ## Stack - Package manager: pnpm workspaces - Build orchestration: Turborepo - Language: TypeScript (strict mode throughout) - Frontend: React 18, React Router v6, Tailwind CSS ## Package Ownership Rules - UI components → packages/ui ONLY - Auth logic → packages/auth ONLY - Stripe/payment logic → packages/payments ONLY - HTTP clients → packages/api-client ONLY - If something doesn't exist in a package, build it there first. ## Before Making Any Change 1. Identify which package owns the behaviour you are changing 2. State the 3-5 files you expect to touch before editing 3. Await approval for changes crossing package boundaries 4. Run: pnpm -r typecheck && pnpm -r test before finishing ## Never - Commit secrets, API keys, or tokens to any file - Add a new npm dependency without stating why existing deps don't cover it - Write raw fetch/axios calls in app code — use packages/api-client - Implement auth or payment logic in app-level code
Security hardening with Claude Code
Claude Code prompt:
Conduct a full security audit of this monorepo and then apply hardening. Phase 1 — Audit (read-only, produce a report): - OWASP Top 10 coverage across all packages and apps - Authentication edge cases: token expiry, session fixation, CSRF - Stripe webhook: signature verification, idempotency, error handling - Dependency audit: run pnpm audit and flag all high/critical CVEs - Secret exposure: scan for hardcoded keys or tokens in code - Input sanitisation: XSS vectors in all user-facing inputs Phase 2 — Hardening (with my approval for each change): For each issue identified, propose the fix, await my approval, then implement. Start with critical severity, then high, then medium. Save the hardening report to docs/security/hardening-report.md.
Adding a new app in one session
Once your shared packages are built and hardened, adding a new React app to the monorepo is a single Claude Code session.
New app scaffolding prompt:
Scaffold a new React app called "partner-portal" inside apps/partner-portal/. Requirements: - Vite + React 18 + TypeScript - Import auth from @ourmonorepo/auth — protected routes, RBAC for role "partner" - Import UI from @ourmonorepo/ui — use PageLayout, Sidebar, DataTable, Button - Import API client from @ourmonorepo/api-client - Import Stripe hooks from @ourmonorepo/payments - Pages to scaffold: Dashboard, Account Settings, Billing - Routing: React Router v6, all routes protected with PartnerRoute wrapper Do not create any new shared components — use what exists in packages/ui. Do not write any auth or payment logic — import from the packages. Run pnpm typecheck after scaffolding and fix any type errors.
A new app that would previously take 2–3 days to set up properly is ready in under an hour.
Frequently asked questions
What is a React monorepo and how is it different from multiple separate repos?
A monorepo is a single Git repository housing multiple applications and shared packages. Instead of a separate repo for each app, all apps live in an apps/ directory and share code from packages/. With separate repos, shared logic gets duplicated and diverges. With a monorepo, it lives in one place, is fixed once, and all apps inherit the fix automatically.
Should I use Turborepo or Nx?
For most React teams building multiple web apps, Turborepo is the right starting point — simpler setup, optimised for JavaScript and TypeScript, integrates cleanly with pnpm, and provides intelligent build caching. Nx is worth considering for larger teams or polyglot monorepos.
How does Claude Code work in a monorepo without getting confused?
The key is a well-written CLAUDE.md at the monorepo root that defines package ownership rules explicitly. Require Claude Code to state which files it expects to touch before making changes. This "plan before execute" pattern prevents the most common failure mode: making changes in the wrong package.
How do I handle Stripe payments across multiple apps?
Build your Stripe integration once in packages/payments and have every app import from it. Webhook signature verification is critical — every event must have its Stripe-Signature header verified before processing.
How long does it take to build the shared package foundation?
With Claude Code and well-structured prompts, the core foundation — auth, payments, UI, API client, shared config — typically takes one to two focused sessions. Each new app then scaffolds from proven foundations rather than rebuilding them.
Next steps
- See the full Claude stack: The Complete Claude AI Stack
- Learn Claude Code fundamentals: 10 Things to Know About Claude Code
- Deploy your Worker APIs: Cloudflare Workers for Serverless Functions
About the author
Paul Sullivan is the Founder of ARISE GTM and creator of the ARISE GTM Methodology®. He is the author of Go To Market Uncovered (Wiley, 2025) and host of the GTM Uncovered podcast.
- Based: Here East, Queen Elizabeth Olympic Park, London E15 2GW
- Work with ARISE GTM: arisegtm.com/agency-services
- Speak to Paul: arisegtm.com/contact-us
- Podcast: GTM Uncovered on Spotify
- YouTube: @gtmuncovered
Based on ARISE GTM's Claude Code deployment engagements (2024–2026). Current as of April 2026.