Skip to content
Community content. Review instructions before giving them to an AI agent — treat modules like open-source code.

Monorepo Conventions

Monorepo conventions for multi-package repositories: explicit package boundaries and dependencies, shared config packages, avoiding circular dependencies, affected-only CI, versioning strategy, and when a monorepo is the wrong answer.

Mby @markdownersPublished August 21, 2026 · ~4 min read

0 downloads · Used by 0 stacks

A monorepo's value comes entirely from explicit, enforceable boundaries between packages — the moment packages reach into each other's internals or the build stops scaling with repo size, it's just one large tangled codebase with extra tooling on top.

Clear package boundaries with explicit dependencies

  • Give every package a defined public surface (its exported entry points) and treat everything else inside it as private — other packages may depend only on the public surface, never on a deep import into another package's internal file structure.
  • Declare every cross-package dependency explicitly in the consuming package's manifest, even within the same workspace — never rely on hoisting or a transitive install to make an undeclared dependency "just work"; it will break the moment the dependency graph shifts.
  • Keep a package's package.json (or equivalent) name matching what other packages actually import, and keep its declared dependencies in sync with what its code actually imports — an unused declared dependency or an undeclared used one are both bugs the tooling should catch.

Shared config packages

  • Centralize cross-cutting configuration (lint rules, TypeScript base config, build tooling config) into a small number of shared config packages that other packages extend, rather than copy-pasting config files into every package and letting them drift.
  • Version and change shared config deliberately — a change to the shared lint/TS config affects every consuming package simultaneously, so treat it with the same care as a breaking API change, not a routine tweak.
  • Keep shared config packages minimal and composable (a base config plus small overrides) rather than one giant config every package must take as-is.

No circular dependencies

  • Design the package dependency graph as a strict DAG — if package A depends on package B, B must never depend (even transitively) back on A; a circular dependency breaks independent builds, independent versioning, and often the build tool's task ordering.
  • Use the monorepo tool's dependency graph command to check for cycles before merging a change that adds a new cross-package import, not after a build starts failing mysteriously.
  • Break an emerging cycle by extracting the shared piece both packages need into a third, lower-level package that both depend on, rather than letting either package import from the other.

Affected-only CI

  • Run lint/test/build only for packages actually affected by a given change (using the dependency graph to compute the affected set), not the entire repository on every commit — a monorepo that reruns everything on every PR stops scaling once the repo grows past a handful of packages.
  • Cache task outputs (build artifacts, test results) keyed by content hash of a package's inputs, so an unaffected package's previous results are reused instead of rerun.
  • Keep the affected-detection logic itself correct and tested — a CI setup that silently skips a package it should have run is worse than one that's merely slow, because it hides real failures.

Versioning strategy

  • Decide explicitly, per monorepo, whether packages are versioned together (a single repo-wide version) or independently (each package versioned on its own) — and apply that decision consistently; a mixed, undocumented approach makes it unclear what a version bump actually implies.
  • For independently versioned packages, use a changeset/changelog tool that captures intended version bumps at PR time (feature = minor, fix = patch, breaking = major) rather than deciding version bumps ad hoc at release time.
  • Pin internal cross-package dependencies to workspace-relative versions during development, and let the release tooling rewrite them to real published version ranges at publish time — don't hand-edit version numbers across packages.

When a monorepo is the wrong answer

  • Don't adopt a monorepo just because a project has multiple packages — a small number of packages with genuinely independent release cadences, ownership, and almost no shared code get little benefit and pay real tooling overhead.
  • Reach for a monorepo specifically when packages are developed together, released together or near-together, and share meaningful code/config — the tighter the coupling between packages, the stronger the case.
  • Split a package out of a monorepo (or avoid merging it in) when its release cycle, ownership, or dependency footprint is genuinely independent enough that the shared build/CI/versioning machinery adds friction rather than removing it.
Badge

Link back to this module from your own README.

Get it on Markdowners
[![Get it on Markdowners](https://markdowners.com/mdstack-badge.svg)](https://markdowners.com/m/markdowners/monorepo-conventions)

Comments (0)

Sign in to comment. Sign in

No comments yet. Be the first to add one.

Discussions about this module

No discussions about this module yet.

Start a discussion
Monorepo Conventions — AI agent instructions — Markdowners