Why We Switched to a Monorepo

Why We Switched to a Monorepo

• 3 min read

We just completed a major infrastructure upgrade: migrating from separate repositories to a unified monorepo structure.

Why the Change?

Brandmine consists of two major applications:

  • Hugo static site (brandmine.ai) - Our public-facing content platform
  • Hub internal app (hub.brandmine.ai) - CRM, deal pipeline, story management

Previously, these lived in separate repos. Every shared update meant duplicate work—copying TypeScript types, config files, syncing data structures.

The problem: Making a simple schema change required editing 6+ files across 2 repos, tracking 2 separate deployments, and hoping nothing fell out of sync.

What We Built

Our monorepo uses modern tooling designed for exactly this scenario:

1. Unified Workspace (pnpm)

Single pnpm install installs all dependencies for both apps. No more jumping between repos to run setup scripts.

2. Shared Packages

We created 4 internal packages that both apps consume:

  • @brandmine/shared-types - Single source of truth for TypeScript interfaces
  • @brandmine/typescript-config - Consistent tsconfig across all projects
  • @brandmine/prettier-config - Unified code formatting
  • @brandmine/eslint-config - Shared linting rules

Result: Change a type definition once, both apps get it instantly.

3. Intelligent Build System (Turborepo)

Turborepo adds caching and dependency-aware task execution:

  • First build: 11.4 seconds
  • Cached build: 204ms (98% faster, 55x speedup)
  • Parallel dev servers: Start both Hugo and Hub simultaneously

The “FULL TURBO” message when cache hits feels unreasonably satisfying.

Real-World Impact

Before Monorepo

# Terminal 1
cd brandmine-hugo
npm install
hugo server

# Terminal 2
cd brandmine-hub
npm install
npm run dev

# Make schema change
# → Edit hugo/data/schema.json
# → Copy to hub/src/types/schema.ts
# → Manually keep in sync
# → Hope you didn't miss anything

After Monorepo

# Single terminal
pnpm dev              # Both apps running
pnpm build            # Both apps built (with caching)

# Make schema change
# → Edit packages/shared-types/index.ts
# → Both apps auto-update
# → Type safety guaranteed

Technical Details

5-Phase Migration:

  1. ✅ Created shared types package
  2. ✅ Switched to pnpm workspaces
  3. ✅ Restructured to apps/ and packages/ layout
  4. ✅ Built shared config packages
  5. ✅ Integrated Turborepo caching

Disk Space: Saved ~500MB (40-50% reduction) by eliminating duplicate dependencies.

Zero Downtime: All migrations happened without affecting production deployments. Cloudflare Pages builds adapted seamlessly.

What This Enables

Easier Hugo ↔ Hub Data Sync

Our sync scripts (sync-to-supabase.js, sync-from-supabase.js) now import shared types directly. No more brittle string-based validation.

Confident Refactoring

TypeScript knows about all usages across both apps. Rename a field? The compiler shows every place that needs updating.

Faster Onboarding

New developers clone one repo, run pnpm install, and they’re ready. No “which repo do I need?” confusion.

Lessons Learned

Filter Names Matter

Turborepo requires exact package names (@brandmine/hugo not hugo). We learned this the hard way when a Cloudflare build failed. Now we test filter names locally first.

Gitignore Patterns for Monorepos

Root-level patterns (/public/) don’t apply to subdirectories (apps/hugo/public/). We fixed this immediately and removed 1000+ accidentally-tracked build artifacts.

Version Syntax Changes

Turborepo v2.x uses tasks instead of deprecated pipeline key. Always check latest docs when implementing from older specs.

Why This Matters

Most monorepo migrations are about scale (Google, Meta managing thousands of services). Ours is about clarity.

When your CTO is Claude and your CEO is switching between business strategy and technical implementation, reducing cognitive overhead matters. One repo, one truth, one command to run everything.

The goal: Spend less time coordinating infrastructure, more time illuminating exceptional brands.

What’s Next

Immediate: Let the monorepo stabilize for 2-3 weeks. We’re not chasing theoretical optimizations.

Future (if needed):

  • Remote caching (Turborepo Cloud)
  • CI/CD optimization (affected task detection)
  • Shared utility packages

For now? We’re building features, not infrastructure.


Full technical breakdown: See our dev journal entry for performance benchmarks, error resolution, and complete implementation details.