Alim Studio

Blog

Lightweight Monorepo Architecture for Small Teams

A guide to building a lightweight, fast, and maintainable monorepo for small teams, complete with tooling strategies, folder structures, and best practices.

Jumadil Abdul Rahman Selian

Published date
Reading time
5 min read
Total views
2 views
Category
Engineering

Introduction

Monorepos are often seen as heavy solutions suitable only for large corporations. In reality, with the right choice of tooling, a monorepo can be the lightest option for small teams wanting to share code, maintain consistency, and accelerate onboarding. This article discusses a monorepo architecture that remains lean without overloading the pipeline and developer workstations.

The goal of this article is to provide an end-to-end guide starting from tool selection, directory structure, workspace configuration, to operational practices for small teams consisting of 2–10 people.

Monorepo illustration for small teams


Understanding Monorepo

A monorepo is a code storage strategy where many projects or packages are stored in a single git repository. Unlike a polyrepo, which separates each service into different repos, a monorepo stores everything in one place using a workspace system.

In the context of small teams, a monorepo provides three main advantages:

  • Full code visibility: team members can read and modify code in other packages without additional cloning processes.
  • Safer cross-package refactoring: a single PR can touch multiple packages simultaneously, and CI validates dependencies.
  • Fast onboarding: new developers only need to clone one repo, run one install command, and immediately become productive.

Monorepo vs Polyrepo for Small Teams

Polyrepos are suitable when each service is truly independent and owned by different teams. For small teams that frequently share utilities, types, or UI components, polyrepos create overhead in the form of code duplication, version drift, and manual synchronization processes.

AspectMonorepoPolyrepoOnboarding1 clone, 1 installClone multiple reposRefactor1 PR, atomicMulti-repo, multi-PRCIRequires path filteringIndependent per repoRepo sizeLargerSmaller per repoSuitable forSmall–medium teamsLarge distributed teams

For small teams, monorepos usually win on daily collaboration aspects, even if the repo size is larger.

Comparison between monorepo and polyrepo


Lightweight Monorepo Tooling

The choice of tools determines whether a monorepo feels light or heavy. For small teams, prioritize tools with minimal configuration, fast installation, and those that do not add large dependencies to every package.


1. npm workspaces

npm version 7+ supports workspaces natively. No need to install additional tools. Simply add the workspaces block in the root package.json.


{
  "name": "monorepo-root",
  "private": true,
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}

Pros: zero extra dependency, already available in Node.js. Cons: limited features compared to specialized tools (e.g., Turborepo or Nx).


2. pnpm workspaces

pnpm uses a content-addressable store so that the same package in multiple workspaces is not duplicated on disk. It saves space and installs faster.


npm install -g pnpm
pnpm init
pnpm add -w typescript

3. monorepo (CLI by mariuslundgard)

The npm package named monorepo is a CLI utility for Node.js monorepo projects. The latest version in the registry is 1.2.2. Official homepage: github.com/mariuslundgard/monorepo.

Note: The official README is not available in the registry, so CLI command details, flags, and configuration options must be verified directly from the GitHub repository above. Do not invent options not listed in the source.


4. Turborepo (optional, when caching is needed)

Turborepo adds build caching and task pipelines. For small teams that run CI frequently, Turborepo drastically cuts build times. However, for projects with 3–5 packages, npm workspaces alone are sufficient.

Lightweight monorepo tooling


Recommended Folder Structure

Folder structure affects how easily the team navigates the repo. For small teams, use two top-level folders: apps/ and packages/.


monorepo/
├── apps/
│   ├── web/            # Next.js / Vite app
│   └── api/            # Express / Fastify service
├── packages/
│   ├── ui/             # shared UI components
│   ├── config/         # eslint, tsconfig, tailwind preset
│   └── utils/          # helper functions
├── package.json
├── pnpm-workspace.yaml
└── tsconfig.base.json

Principles:

  • apps/ contains deployable units (web, api, worker).
  • packages/ contains libraries imported by apps or other packages.
  • Shared configurations (eslint, tsconfig, prettier) are placed in packages/config to avoid duplication.

Practical Workspace Configuration

This section shows the minimal configuration for a pnpm-based monorepo, as it is the lightest for small teams.


packages:
  - "apps/*"
  - "packages/*"

The pnpm -r command runs scripts in all packages. The --parallel flag speeds up tasks like dev that run concurrently.


Dependency and Versioning Strategy

A common mistake in small team monorepos is mixing dependency versions between packages. Set a strategy from the start:

  • Hoist common dependencies (react, typescript, eslint) to the root package.json.
  • Keep specific dependencies (database drivers, UI frameworks) in the package that needs them.
  • Use peerDependencies for UI packages so the React version is determined by the app, not pinned.
  • Avoid duplicate React: if two versions of React appear in node_modules, add resolutions at the root.
{
  "resolutions": {
    "react": "^18.3.0",
    "react-dom": "^18.3.0"
  }
}

Keeping CI/CD Fast

Monorepos often fail in CI because the CI runs all tests for all packages on every push. The solution: filter based on the changed path.


GitHub Actions with path filters

name: CI
on:
  push:
    branches: [main]
  pull_request:

jobs:
  changed:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v3
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm -r --filter "./[origin/main...HEAD]" run test
      - run: pnpm -r --filter "./[origin/main...HEAD]" run build

The --filter flag ensures that only packages that have changed (or dependencies of changed packages) are tested. This significantly cuts CI time.

Monorepo CI pipeline with path filter


Best Practices for Small Teams

  • Start with 2–3 packages. Do not over-engineer. Add new packages only when code is truly used in ≥2 places.
  • Conventional commits + changesets. Use Changesets for versioning and automatic changelogs when packages are published.
  • Shared linting. Place ESLint and Prettier configurations in packages/config and extend them in each package.
  • TypeScript project references. Enable these in tsconfig.base.json so the IDE can navigate between packages quickly.
  • Single lockfile. Do not let each package have its own lockfile. Use one pnpm-lock.yaml or package-lock.json at the root.
  • Brief documentation at the root. The README.md file should contain: how to install, how to run dev, how to add a new package, and how to publish.

Common Troubleshooting

Issue: dependency not linked

Cause: packages in packages/ are imported using relative paths or the wrong name.

Debug: check the name in the target package's package.json, then import according to that name.


{
  "name": "@repo/utils",
  "version": "0.1.0",
  "main": "./src/index.ts"
}

Issue: duplicate dependency versions

Cause: each package declares its own version for the same dependency.

Debug: run pnpm why react to see the dependency tree.

Fix: hoist to root or use resolutions.


Issue: slow CI

Cause: CI runs all packages without a filter.

Fix: use --filter based on diff, or adopt Turborepo with a remote cache.


Issue: TypeScript does not recognize paths between packages

Cause: tsconfig is not using project references.

Fix: create a tsconfig.base.json with composite: true and references in each package's tsconfig.json.

Monorepo troubleshooting


Lightweight Tool Comparison

ToolExtra dependencyInstall speedSuitable fornpm workspacesNoneFairly fastSmall teams, simple projectspnpm workspacespnpmFast, disk-efficientSmall–medium teamsmonorepo (mariuslundgard)npm packageDepends on CLI Node.js monorepo projectsTurborepoturborepoFast with cacheTeams needing fast CI NxnxHeavy at startLarge teams, complex monorepos


When You Don't Need a Monorepo

Monorepos are not a universal solution. Avoid a monorepo if:

  • Each service is owned by different teams with independent release cycles.
  • The repo is approaching size limits that make cloning slow (e.g., >5 GB).
  • There is no code that is truly shared between projects.

In the above cases, a polyrepo remains healthier.


Conclusion

A monorepo for small teams is not about adding as many tools as possible, but about choosing sufficient structure and tooling. Start with npm or pnpm workspaces, an apps/ + packages/ structure, shared configuration in one place, and a CI that filters changes. Add Turborepo or Changesets only when truly needed.

With such discipline, a monorepo remains lightweight: fast installation, short CI, smooth onboarding, and safe refactoring. Small teams can focus on the product, not on tooling overhead.

Lightweight monorepo architecture summary

  • monorepo
  • dx
  • typescript