From monolith to monorepo: how Stackseed is structured
Three apps, twelve packages, zero business logic in app code. A look at how Turborepo, Bun, and strict package boundaries keep everything clean as you scale.
Stackseed is a Turborepo monorepo managed by Bun. Three apps share twelve packages. Here's why this structure matters and how it works.
The three apps:
apps/web — the authenticated dashboard, settings pages, and all API routes. Runs on port 3000.
apps/marketing — the public-facing site: landing page, pricing, blog, docs, changelog. Runs on port 3002.
apps/expo — the React Native mobile app for iOS and Android.
The key principle: apps contain no business logic. All shared logic lives in packages. This means the mobile app and web app use the same auth, payment, and data access code. No duplication, no drift.
The twelve packages cover everything: auth, database, payments, email, background jobs, analytics, messaging, CMS, types, utilities, and UI components. Each package has a clear boundary and a typed public API.
All side effects go through Trigger.dev. Apps never call external services inline. When a user signs up, the API route triggers a background task that sends a welcome email, logs an analytics event, and creates a notification — all in parallel, all typed, all observable.
This structure has a second benefit: it's extremely AI-friendly. Because every package follows the same patterns and every boundary is typed, an AI agent can navigate the entire codebase with confidence. It knows where auth lives, how payments work, and what types to use. No guessing required.
We deploy as two separate Vercel projects (web and marketing) that share the same backend. The mobile app ships through Expo. Everything stays in sync because it shares the same packages.
That's the last one for now.
Back to all posts