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.

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.

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 typescript3. 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.

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.jsonPrinciples:
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/configto 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
peerDependenciesfor 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
resolutionsat 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 buildThe --filter flag ensures that only packages that have changed (or dependencies of changed packages) are tested. This significantly cuts CI time.

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/configand extend them in each package. - TypeScript project references. Enable these in
tsconfig.base.jsonso the IDE can navigate between packages quickly. - Single lockfile. Do not let each package have its own lockfile. Use one
pnpm-lock.yamlorpackage-lock.jsonat the root. - Brief documentation at the root. The
README.mdfile 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.

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.

