Monorepos: Should This Be Its Own Repo?
The real question isn't whether to use a monorepo. It's whether your packages need independent versioning and releases. If not, keep it together.
Monorepos: Should This Be Its Own Repo?
"Should I have a .git folder in the packages, or continue to push into the monorepo? It feels weird."Short answer: do not put .git folders inside packages/.
Keep pushing everything in the monorepo.
What you're feeling is normal -- you're starting to design things as independent packages, but that doesn't mean they need independent Git repos.
Let's clarify the tradeoffs.
Monorepo vs. Multi-Repo
When you have tightly-coupled packages that evolve together, a monorepo is exactly the right choice. Consider an ecosystem like this:
1packages/2 cli/3 plugin-kit/4 ui/5 plugins/6 localization/7 fontsource/8 thematic/9 infra/10 studio/11 apps/These packages depend on each other. They share types, conventions, and release cycles. That is a textbook monorepo case.
Why Nested .git Repos Will Hurt You
If you add .git inside packages/plugins/localization, you create a sub-repository. That causes real problems:
- Tooling confusion -- pnpm/bun workspace and git boundaries mismatch
- Harder refactors across packages
- CI complexity -- multiple checkout strategies, multiple pipelines
- Version drift -- packages slowly desync
- Accidental detached histories
- Broken commits when moving files across boundaries
And you gain almost nothing right now.
Watch out
Nested .git repos inside a monorepo create sub-repositories that most tooling doesn't handle well. Your workspace manager, CI, and refactoring tools all assume a single .git root.
The Mental Model You Want
Think of this:
Packages are logical boundaries, not repository boundaries.You're already doing the right thing if your structure looks like this:
1packages/2 plugin-kit/3 cli/4 plugins/5 localization/6 fontsource/7 thematic/Each plugin is:
- Independently installable in apps
- Independently testable
- Independently typed
- Independently versionable (later, when you need it)
But still part of the same repo. That's ideal.
When Separate Repos Actually Make Sense
Only split when you hit real triggers:
- Plugins become open-source projects with external contributors
- Plugins have independent release cycles managed by different teams
- Different teams maintain different packages with different review processes
- You publish to npm separately and consumers don't need the rest
- External contributors work only on a subset and shouldn't see the rest
You're probably not there yet.
And honestly, most projects never need that split. Even big ecosystems often keep everything together:
- React repo contains multiple packages
- Expo repo contains many packages
- Turborepo examples use monorepos
- Vercel uses monorepos extensively
- Nx workspaces are built around the monorepo concept
Setting Up the Workspace
A well-configured workspace is what makes monorepos actually work. Here's a minimal pnpm-workspace.yaml:
1packages:2 - "packages/*"3 - "packages/plugins/*"4 - "apps/*"And your root package.json scripts tie it all together:
1{2 "scripts": {3 "build": "turbo build",4 "dev": "turbo dev",5 "test": "turbo test",6 "lint": "turbo lint",7 "typecheck": "turbo typecheck"8 }9}Each package has its own package.json with its own dependencies, scripts, and entry points. They reference each other using workspace protocol:
1{2 "name": "@myorg/cli",3 "dependencies": {4 "@myorg/plugin-kit": "workspace:*",5 "@myorg/ui": "workspace:*"6 }7}The Task Pipeline
The real magic is in the dependency-aware task runner. With Turborepo:
1{2 "tasks": {3 "build": {4 "dependsOn": ["^build"],5 "outputs": ["dist/**"]6 },7 "test": {8 "dependsOn": ["build"]9 },10 "dev": {11 "cache": false,12 "persistent": true13 }14 }15}The ^build syntax means "build my dependencies first." This ensures when you run turbo build, packages build in the right order. No manual orchestration needed.
Handling Shared Configuration
One of the biggest wins of a monorepo: shared config. No more copy-pasting tsconfig.json and .eslintrc across 15 repos.
1{2 "compilerOptions": {3 "strict": true,4 "target": "ES2022",5 "module": "ESNext",6 "moduleResolution": "bundler",7 "declaration": true,8 "declarationMap": true,9 "sourceMap": true10 }11}Each package extends it:
1{2 "extends": "@myorg/tsconfig/base.json",3 "compilerOptions": {4 "outDir": "dist",5 "rootDir": "src"6 },7 "include": ["src"]8}One source of truth. Change it once, every package picks it up.
The Versioning Question
When you do need independent versioning (publishing to npm, external consumers), use changesets:
1npx changesetThis creates a versioning intent file. When you're ready to release:
1npx changeset version2npx changeset publishBut here's the key: you don't need this until you're actually publishing packages externally. For internal packages, just use workspace:* and move on.
Common Pitfalls
1. Over-splitting too early
Don't create 20 packages on day one. Start with a few and split when boundaries become clear through actual usage.
2. Circular dependencies
Package A imports from B, B imports from A. This is a sign your boundaries are wrong. Refactor the shared code into a third package.
3. Ignoring the dependency graph
If you change a core package, everything that depends on it needs to be tested. Don't skip this -- use turbo with --filter to run only affected tests:
1turbo test --filter=...@myorg/plugin-kit4. Giant root package.json
Keep the root minimal. Build scripts and dev dependencies only. Actual code dependencies belong in their respective packages.
Guiding Rule
If you ever ask yourself: "Should this be its own repo?" -- the better question is: "Does this need independent versioning and releases?" If not, keep it in the monorepo.

Monorepo git history flowing as a unified stream
The monorepo isn't about having one giant project. It's about having one source of truth for code that evolves together. Logical boundaries without physical fragmentation.
Keep it simple. Keep it together. Split only when reality demands it.