Monorepo Overview
Understanding the monorepo structure and how apps are organized.
What is a Monorepo?
A monorepo (monolithic repository) is a single repository that contains multiple projects, applications, and shared code. Instead of having separate repositories for each app, everything lives together in one place.
Benefits for Vibe Coders
- Shared code: Common utilities, types, and configurations are shared across all apps
- Consistent patterns: All apps follow the same structure and conventions
- Single source of truth: One
CLAUDE.mdfile contains all the guidelines - Easier collaboration: Claude can understand the entire codebase context
Repository Structure
vibe-coding-platform/
├── apps/ # All applications live here
│ ├── stock-check/ # Example app
│ ├── breakfast-tracker/ # Another app
│ └── your-new-app/ # Your app goes here
├── shared/ # Shared resources (exactly these three)
│ ├── database/ # Database schema (Prisma)
│ ├── auth/ # Auth API + Auth UI
│ └── schema.md # Human-readable table reference
├── templates/ # App templates for new projects
├── CLAUDE.md # AI development guidelines
└── package.json # Root workspace configuration
Platform Architecture
The foundation platform provides:
- Centralized authentication via Auth UI + Auth API (magic link, passkeys, optional device passwords)
- One Postgres database per company (
DATABASE_URLvia Neon) for auth tables and app data - App Portal as the launcher for all deployed apps
- GitHub Actions → Railway deploys with Cloudflare DNS for custom domains
Each app under apps/ is a standalone Next.js service. Apps authenticate through shared middleware and store data in the company database using the pg library.
Custom domains (staging and production)
After each deploy, CI creates a Railway custom domain and corresponding Cloudflare DNS records:
- Domain pattern:
{service-name}.{RAILWAY_APP_DOMAIN_SUFFIX} - Set
RAILWAY_APP_DOMAIN_SUFFIXper GitHub Environment (e.g.apps.staging.acme.com/apps.acme.com) - Requires
CLOUDFLARE_API_TOKENandCLOUDFLARE_ZONE_IDin that environment
The apps/ Directory
Each app in the apps/ directory is a standalone Next.js application with its own:
package.json- Dependencies and scriptsapp/directory - Next.js App Router pagesdeploy.config.yml- Railway deployment configuration
App Naming Convention
Apps use kebab-case (lowercase with hyphens):
| ✅ Correct | ❌ Incorrect |
|---|---|
stock-check | StockCheck |
breakfast-tracker | breakfast_tracker |
my-new-app | myNewApp |
The shared/ Directory
Shared resources that multiple apps can use. There are exactly three entries — run ls shared/ to confirm:
| Path | Purpose | Who Can Edit |
|---|---|---|
shared/database/ | Prisma schema for all tables | Vibe coders (with review) |
shared/auth/ | Centralized authentication service (auth-api/, auth-ui/) | Developers only |
shared/schema.md | Human-readable reference for every table | Vibe coders (with review) |
Deeper guides: Database, Auth & Access, File Storage.
Why Storage and Types Aren't in shared/
Each app builds and deploys on its own, so an app cannot import runtime code from shared/. Anything your app executes has to live inside your app. The capabilities below are real — they are turned on per app instead of sitting in a shared directory:
| Capability | How your app gets it |
|---|---|
| File storage | Copy apps/example-app/app/_lib/storage.ts into your app, then map BUCKET_* under secrets: in your own deploy.config.yml. Full walkthrough: File Storage |
| Notification email | Copy apps/example-app/app/_lib/email.ts the same way, mapping APP_EMAIL_* under secrets: |
| TypeScript types | Declare them inside your app, next to the code that uses them |
Opting in per app is deliberate: an app that stores no files never holds a bucket key, and an app that sends no mail never holds a mail key.
Database Access
Apps use the pg library (not Prisma client) for runtime database queries. Use the getDb() helper from the template and always use parameterized queries ($1, $2, …) to prevent SQL injection:
import { getDb } from '@/app/_lib/db';
const db = getDb();
const result = await db.query(
'SELECT * FROM my_app_users WHERE id = $1',
[userId]
);
const users = result.rows;Note: Database queries only execute in deployed environments (PR preview or production). See Database for the full schema and migration workflow.
Key Files to Know
CLAUDE.md (Root)
The most important file for AI development. Contains:
- Coding standards and conventions
- Git workflow rules
- Database guidelines
- Deployment instructions
App-Specific Files
Each app may have its own documentation:
| File | Purpose |
|---|---|
README.md | App overview and setup instructions |
CLAUDE.md | App-specific AI guidelines |
PLAN.md | Development progress and planned work |
Working with the Monorepo
Running Commands
Always run commands from the repository root. You don't run apps locally — everything runs on staging and production after you open a PR (see Deployment). The one command you do run locally is the build, to reproduce the CI check before committing:
# Verify the build passes before committing (same check CI runs)
pnpm build:your-app-nameInstalling Dependencies
Never run pnpm install inside an app directory:
# ✅ Correct: Run from repository root
pnpm install
# ❌ Wrong: Don't run inside app directory
cd apps/my-app && pnpm installAdding a New Dependency
# Add to a specific app
pnpm --filter my-app-name add date-fns
# Add as dev dependency
pnpm --filter my-app-name add -D @types/some-packageQuiz
Where should you run pnpm install?
Next Steps
- Learn about Branching & PRs for the git workflow
- Review the Workflow for day-to-day development