How to Structure a Monorepo for Multiple Products

When your side project turns into three side projects, you face a choice: separate repos or monorepo? I started with separate repos for each RAXXO product and merged them into a monorepo after the third project shared the same utility functions. Here's what I learned and how to set it up without losing your mind.

When a Monorepo Makes Sense

A monorepo isn't always the right call. It makes sense when:

  • Multiple projects share code (types, utilities, design tokens)
  • You want atomic changes across projects (update a shared type, fix all consumers in one commit)
  • One person (or a small team) works across all projects
  • You want consistent tooling (ESLint config, TypeScript settings, formatting)

It doesn't make sense when projects are truly independent, use different tech stacks, or are worked on by separate teams who'd step on each other.

Directory Structure

Here's a practical structure for a studio with multiple products:

raxxo-studios/
  app/                    # Main Next.js app (SaaS product)
  components/             # Shared React components
  lib/                    # Shared libraries
  shop/                   # Shopify theme files
  lexxa/                  # Content pipeline
  docs/                   # Documentation
  scripts/                # Build and automation scripts
  packages/               # Shared packages (if needed)
    design-tokens/
    types/
  package.json            # Root package.json
  tsconfig.json           # Base TypeScript config

The key insight: not everything needs to be a "package." If your shared code lives in lib/ and is imported with path aliases, that's fine. You don't need Turborepo, Nx, or workspace protocols until the complexity justifies it.

Path Aliases Over Workspace Packages

For a solo developer, TypeScript path aliases are simpler than npm workspace packages:

// tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@/lib/*": ["./lib/*"],
      "@/components/*": ["./components/*"],
      "@/types/*": ["./packages/types/*"]
    }
  }
}

Every file in the repo can import from @/lib/auth or @/components/Button. No package.json in every directory, no version management, no publishing step. Just imports.

Shared Configuration

One of the biggest wins of a monorepo is consistent configuration. One ESLint config, one Prettier config, one TypeScript base config. When you decide to enable a stricter lint rule, it applies everywhere.

Keep your base configs in the root and let specific projects extend them if needed:

// Individual project tsconfig
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist"
  },
  "include": ["src"]
}

Build and Deploy Independence

Even though everything lives in one repo, each product should build and deploy independently. The Next.js app deploys to Vercel. The Shopify sections get pasted into the theme editor. The Lexxa pipeline runs its own scripts.

Your CI should know which projects are affected by a change and only build those. For a solo developer, this might just be running the right deploy command manually. For a team, tools like Turborepo or Nx handle this automatically.

The Shared Types Pattern

The most valuable shared code is types. When your Shopify webhook handler and your Next.js API route both deal with the same plan types, define them once:

// lib/types.ts
export type PlanId = 'free' | 'flame' | 'blaze' | 'neon';
export type PlanLimits = {
  generations: number;
  hashtagRegens: number;
  musicSuggests: number;
  brandProfiles: number;
  model: string;
};

Change the type in one place, TypeScript catches every file that needs updating. This alone justifies the monorepo for me.

Git Workflow

In a monorepo, your git history contains changes to all projects. This can get noisy. A few practices that help:

  • Prefix commit messages with the project: [studio] Add brand profiles, [shop] Update pricing section, [lexxa] New episode script
  • Use feature branches for multi-file changes
  • Keep non-code assets (videos, large images) out of git. Use .gitignore aggressively

When to Add a Build Tool

Start without Turborepo, Nx, or Lerna. Add them when you need:

  • Parallel builds across 3+ projects
  • Build caching (skip rebuilding unchanged projects)
  • Dependency graph-aware task running
  • More than one developer working simultaneously

Want the complete blueprint?

We're packaging our full production systems, prompt libraries, and automation configs into premium guides. Stay tuned at raxxo.shop

For a solo developer with 2-3 projects, the overhead of a build orchestrator usually isn't worth it. A few shell scripts in a scripts/ directory do the job.

Real-World Example

The RAXXO Studios repo contains a Next.js SaaS app, a Shopify store's custom sections, an AI content pipeline, documentation, and automation scripts. They share TypeScript types, design tokens, and utility functions. The whole thing builds on one npm run build for the Next.js app, while the Shopify sections and Lexxa pipeline work independently.

It's not fancy. It's not a cutting-edge monorepo with 47 packages and a custom build pipeline. It's a folder with related projects that share code. And that's exactly what a solo developer needs.

Built with a monorepo structure. Explore the SaaS product at studio.raxxo.shop.

Dieser Artikel enthält Affiliate-Links. Wenn du dich darüber anmeldest, erhalte ich eine kleine Provision - für dich entstehen keine Mehrkosten. Ich empfehle nur Tools, die ich selbst nutze. (Werbung)