Monorepo vs Polyrepo
Two different ways to organize a project with multiple apps or services. The tradeoff is between shared code convenience (monorepo) and deployment independence (polyrepo).
What is a Monorepo
One Git repository, multiple apps or packages inside.
my-project/ ← single repo
├── apps/
│ ├── web/ → Next.js frontend (marketing)
│ ├── app/ → Next.js app (dashboard)
│ └── api/ → Express backend
├── packages/
│ ├── ui/ → shared component library
│ ├── types/ → shared TypeScript types
│ └── config/ → shared ESLint, Tailwind config
├── package.json
└── turbo.json
One git push can trigger builds for all affected apps. Shared code lives in packages/ and is imported directly without publishing to npm.
Tools: Turborepo, Nx, pnpm workspaces
What is a Polyrepo
Each app or service lives in its own separate Git repository.
Real example: Neko Singa AI
github.com/nekosinga/web → marketing site
github.com/nekosinga/app → main application (AI tools)
github.com/nekosinga/api → backend API
github.com/nekosinga/docs → documentation
Each repo deploys independently to its own Vercel project:
| Repo | Vercel Project | Live URL |
|---|---|---|
| web | nekosinga-web | nekosinga.vercel.app |
| app | nekosinga-app | app-nekosinga.vercel.app |
| api | nekosinga-api | api-nekosinga.vercel.app |
| docs | nekosinga-docs | docs-nekosinga.vercel.app |
Each repo moves at its own pace. The docs can be updated without touching the app. The API can be redeployed without rebuilding the marketing site.
When to Use Which
| Situation | Use |
|---|---|
| Solo project, everything tightly coupled | Monorepo |
| Small team, shared components everywhere | Monorepo |
| Multiple apps with independent deploy cadences | Polyrepo |
| Different teams owning different services | Polyrepo |
| Public-facing site + internal dashboard + API | Polyrepo |
| AI product with web, app, API, docs | Polyrepo (like Neko Singa) |
For Neko Singa I chose polyrepo because:
- The marketing site (
web) and the AI dashboard (app) have completely different update cycles - The API can be redeployed independently when endpoints change
- Docs can be written and published without triggering a full app rebuild
- Each Vercel project has its own environment variables
Polyrepo: How the Services Talk to Each Other
Each service exposes a URL that others call via environment variables.
# nekosinga/app .env.local
NEXT_PUBLIC_API_URL="https://api-nekosinga.vercel.app"
# nekosinga/api .env
ALLOWED_ORIGINS="https://app-nekosinga.vercel.app,https://nekosinga.vercel.app"
DATABASE_URL="postgresql://..."
The app frontend never hardcodes the API URL. It reads from NEXT_PUBLIC_API_URL. Switching from staging to production is just an env variable change in Vercel.
Turborepo Setup (if you go monorepo instead)
npx create-turbo@latest
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
// root package.json
{
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"dev": "turbo dev",
"build": "turbo build"
}
}
Turbo only rebuilds what changed. If you edit only packages/ui, only apps that import from ui get rebuilt.
Shared Packages Pattern (monorepo)
packages/ui/
├── src/
│ ├── Button.tsx
│ └── index.ts
└── package.json
// packages/ui/package.json
{
"name": "@nekosinga/ui",
"main": "./src/index.ts",
"exports": { ".": "./src/index.ts" }
}
// apps/web/app/page.tsx
import { Button } from "@nekosinga/ui";
No npm publish needed. Turborepo resolves the package locally at build time.
Last updated: September 2026.