I'm building 8 apps simultaneously. Not as a side-project weekend warrior — these are production products in a real portfolio.
Here's the architecture that keeps it from being a nightmare.
The Problem Nobody Talks About
Indie devs and small teams face a scaling problem that's different from big companies:
Big companies scale by adding people. Solo devs scale by adding products.
But every new app brings:
- New auth flow
- New billing integration
- New deployment pipeline
- New analytics setup
- New monitoring config
- New domain + SSL
- New bug reports at 3 AM
By app #5, you're not building products — you're maintaining infrastructure.
The Solution: Shared Infrastructure Layer
Instead of 8 separate monoliths, I built a shared foundation that every app inherits:
1. Unified Auth (NextAuth.js)
Every app uses the same auth provider, same session management, same user table structure.
// apps/app-a/auth/[...nextauth]/route.ts
import { baseAuthConfig } from "@shared/auth";
export const authOptions = {
...baseAuthConfig,
providers: [/* app-specific providers */],
};
One change to session logic propagates everywhere. No more "I fixed this in app 3 but forgot app 7."
2. Shared Billing Layer
Instead of integrating Stripe/Lemon Squeezy 8 times:
- One
@shared/billingpackage handles checkout, webhooks, and subscription management - Each app just specifies its plan tiers and prices
- Webhook routing is centralized — one endpoint handles all apps
Result: when payment logic needs updating, it's one codebase, not eight.
3. Monorepo with Turborepo
poria/
├── packages/
│ ├── @shared/ui # Component library
│ ├── @shared/auth # Auth configuration
│ ├── @shared/billing # Payment handling
│ ├── @shared/analytics # Tracking & events
│ └── @shared/db # Database schemas & Prisma client
├── apps/
│ ├── app-a/ # Product A (Next.js)
│ ├── app-b/ # Product B (Next.js)
│ └── ... # 6 more
└── turbo.json
Turborepo caches builds across apps. Change @shared/ui and only affected apps rebuild.
4. Unified Deployment Pipeline
Every app deploys to Vercel with the same config pattern:
{
"buildCommand": "turbo run build --filter=app-a...",
"env": { "SHARED_*": "from monorepo root" }
}
One CI/CD workflow handles all 8 apps. If one fails, the others still deploy.
5. Centralized Observability
Instead of 8 Sentry projects and 8 Analytics dashboards:
- One Sentry org, one project per app (tags for cross-app queries)
- One analytics workspace with app-level segmentation
- One logging pipeline with app identifier in every event
When something breaks, I can see which app, which version, and whether it's correlated across apps.
What This Actually Costs
Setup time: ~40 hours for the shared layer
Per-app marginal cost: ~4-8 hours instead of 20-30
That's the math. First app takes the longest. Each subsequent app gets faster because you're not re-inventing auth, billing, or deployment.
The Hard Lessons
- Don't over-abstract early. Wait until you see the pattern repeat 2-3 times before extracting to shared.
- Version your shared packages. Breaking changes in shared code can take down 8 apps at once.
- Keep app-specific logic app-specific. Shared packages are for infrastructure, not business logic.
- Document everything. Future you (at 2 AM debugging app #6) will thank present you.
The Result
After building this system:
- New app prototype: 2 days instead of 2 weeks
- Bug fixes in shared code: fix once, deploy everywhere
- Onboarding: any new app inherits auth, billing, analytics on day one
- Mental load: instead of 8 separate systems, it's 1 system + 8 frontends
Takeaway
If you're building multiple products as a solo dev or small team, stop treating each app as a greenfield project.
The shared infrastructure layer is your force multiplier. Invest in it early, but don't over-engineer. Let the patterns emerge from real repetition, then extract.
Your future self managing 8 products will thank you.
Building in public at Codcompass — a developer knowledge base. Follow along for more technical deep dives on building products at scale.
Top comments (0)