Monorepo Migration Design
[S1] Problem
The current frontend is a single TanStack Start SPA in frontend/. We need to create an admin panel for user management and analytics, reusing existing infrastructure. A monorepo approach allows shared code (UI components, API layer, hooks) while keeping apps independent for deployment.
[S2] Solution Overview
Transform frontend/ into a Bun workspaces monorepo with Turborepo for build orchestration. Create shared packages for reusable code, and separate apps for the main SPA and admin panel.
Structure
frontend/ # Monorepo root
├── packages/
│ ├── ui/ # shadcn/ui components, styles, hooks
│ ├── api/ # Orval-generated hooks, query keys, invalidation
│ ├── shared/ # Auth helpers, session management, utils
│ └── editor/ # TipTap editor components
├── apps/
│ ├── web/ # Current TanStack Start SPA (moved from frontend/src/)
│ └── admin/ # New admin SPA (React + Vite)
├── package.json # Root workspace config
├── turbo.json # Turborepo pipeline config
└── bun.lock # Lock fileBackend (unchanged)
django-boilerplate/
├── apps/ # Django backend (not touched)
├── assets/ # Django templates (not touched)
└── ...[S3] Shared Packages
packages/ui
Purpose: Reusable UI components, styles, and design tokens.
Contents:
- All shadcn/ui components (
components/ui/) - App-specific UI components (
components/circle/,components/events/, etc.) - CSS variables and Tailwind configuration
- Hooks:
useTheme,useDebounce,use-mobile - Utils:
cn(),formatName(),formatDate()
Exports:
{
"exports": {
".": "./src/index.ts",
"./styles": "./src/styles.css"
}
}Dependencies: React, Tailwind CSS, Radix UI primitives, CVA, clsx, tailwind-merge
packages/api
Purpose: Orval-generated API client and query management.
Contents:
generated.ts(Orval output)hooks.ts(composed hooks)queries.ts(query key factories)invalidation.ts(mutation invalidation helpers)mutator.ts(custom fetch wrapper)
Exports:
{
"exports": {
".": "./src/index.ts"
}
}Dependencies: @tanstack/react-query, Orval runtime
packages/shared
Purpose: Core utilities, auth helpers, and server-side logic.
Contents:
auth.ts(authentication helpers)session.server.ts(Upstash Redis session management)config.server.ts(environment config)error-capture.ts(error handling)utils.ts(general utilities)
Exports:
{
"exports": {
".": "./src/index.ts",
"./server": "./src/server.ts"
}
}Dependencies: @upstash/redis, zod
packages/editor
Purpose: TipTap rich text editor components.
Contents:
TipTapEditor.tsx,TipTapEditorLazy.tsxEditorToolbar.tsxMentionList.tsx- Custom extensions
Exports:
{
"exports": {
".": "./src/index.ts"
}
}Dependencies: @tiptap/react, @tiptap/starter-kit, TipTap extensions
[S4] Apps
apps/web (Current SPA)
Framework: TanStack Start (React 19 + TanStack Router + SSR)
Port: 3000
Deploy: Cloudflare Workers
Changes:
- Move
frontend/src/→apps/web/src/ - Move
frontend/public/→apps/web/public/ - Move
frontend/server.ts,start.ts,router.tsx→apps/web/ - Update imports to use
@repo/ui,@repo/api, etc. - Update
vite.config.tsto reference workspace packages - Keep existing
wrangler.jsonc(if exists) or create new one
Scripts:
{
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"generate:api": "curl -s http://localhost:8000/api/schema/ > ../api/openapi.json && bunx orval"
}
}apps/admin (New Admin SPA)
Framework: React 19 + Vite + TanStack Router + TanStack Query (no SSR)
Port: 3001
Deploy: Cloudflare Workers (separate worker)
Scope:
- User management (list, ban, change roles, view activity)
- Analytics and reports (usage metrics, engagement, growth)
Routes:
/admin # Dashboard with metrics
/admin/users # User list with filters
/admin/users/:id # User detail
/admin/analytics # Reports and chartsStructure:
apps/admin/
├── src/
│ ├── routes/
│ │ ├── __root.tsx
│ │ ├── index.tsx # Dashboard
│ │ ├── users.tsx # User list
│ │ ├── users.$userId.tsx # User detail
│ │ └── analytics.tsx # Analytics
│ ├── components/
│ │ ├── Dashboard/
│ │ ├── Users/
│ │ └── Analytics/
│ ├── main.tsx
│ └── styles.css
├── package.json
├── vite.config.ts
├── tsconfig.json
└── wrangler.jsoncScripts:
{
"scripts": {
"dev": "vite --port 3001",
"build": "vite build",
"preview": "vite preview",
"deploy": "wrangler deploy"
}
}[S5] Authentication
Both apps share the same JWT authentication:
- Token storage: Upstash Redis-backed sessions (via
packages/shared) - Token refresh: Automatic refresh via
/api/token/refresh/ - User context: Same
CustomUsermodel, sameMembershipresolution - API calls: Same
X-Community-Slugheader for tenant resolution
Admin-specific auth:
- Check if user has
is_stafforis_superuserflag onCustomUsermodel before accessing/admin/*routes - Redirect non-admin users to main app (
/) - Use same JWT token, just different route protection
- Admin routes protected via
ProtectedRoutecomponent with role check
[S6] Build & Development
Turborepo Pipeline
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {},
"type-check": {
"dependsOn": ["^build"]
}
}
}Root Scripts
{
"scripts": {
"dev": "turbo dev",
"dev:web": "turbo dev --filter=web",
"dev:admin": "turbo dev --filter=admin",
"build": "turbo build",
"build:web": "turbo build --filter=web",
"build:admin": "turbo build --filter=admin",
"lint": "turbo lint",
"type-check": "turbo type-check",
"generate:api": "cd packages/api && bun run generate"
}
}Development Workflow
# Start all apps
bun dev
# Start only web
bun dev:web
# Start only admin
bun dev:admin
# Build all
bun build
# Generate API client
bun generate:api[S7] Deployment
Cloudflare Workers
Each app deploys independently:
apps/web:
- Worker name:
web - URL:
web.seudominio.com - Deploy:
cd apps/web && bun run deploy
apps/admin:
- Worker name:
admin - URL:
admin.seudominio.com - Deploy:
cd apps/admin && bun run deploy
Makefile Updates
# TanStack SPA (current)
f-dev: # Start web dev server
f-build: # Build web for production
# Admin SPA (new)
f-admin-dev: # Start admin dev server
f-admin-build: # Build admin for production
# Monorepo
f-install: # Install all dependencies
f-lint: # Lint all packages
f-type-check: # Type check all packages[S8] Migration Steps
Important: Directory Handling
The current frontend/ directory becomes the monorepo root. We do NOT create a new directory. Instead:
frontend/src/moves tofrontend/apps/web/src/frontend/public/moves tofrontend/apps/web/public/- Config files stay in
frontend/(root level) - New
packages/andapps/directories are created insidefrontend/
This minimizes git history disruption and keeps the same working directory.
Phase 1: Setup Monorepo Structure
- Create
frontend/packages/directory structure - Create
frontend/apps/web/andfrontend/apps/admin/directories - Move shared code from
frontend/src/to packages - Update root
frontend/package.jsonwith workspaces config - Create
frontend/turbo.json - Verify existing code still works (no import changes yet)
Phase 2: Move Web App
- Move
frontend/src/→frontend/apps/web/src/ - Move
frontend/public/→frontend/apps/web/public/ - Move
frontend/server.ts,start.ts,router.tsx→frontend/apps/web/ - Move
frontend/vite.config.ts→frontend/apps/web/vite.config.ts - Move
frontend/tsconfig.json→frontend/apps/web/tsconfig.json - Update all imports to use
@repo/ui,@repo/api, etc. - Verify web app works (
cd apps/web && bun dev)
Phase 3: Create Admin App
- Scaffold
frontend/apps/admin/with Vite + React - Setup TanStack Router and Query
- Create basic layout and auth protection
- Implement user management features
- Implement analytics features
Phase 4: Deploy
- Create
wrangler.jsoncfor admin - Update root Makefile with new commands
- Test deployment for both apps
- Update DNS for admin subdomain
[S9] Risks & Mitigations
Risk: Import path breakage
Mitigation: Use TypeScript path aliases (@repo/ui, @repo/api) and verify with type-check after each phase.
Risk: Circular dependencies between packages
Mitigation: Clear dependency hierarchy:
uidepends on nothing (or only React)shareddepends on nothingapidepends onsharededitordepends onui- Apps depend on all packages
Risk: Build caching issues
Mitigation: Use Turborepo's --force flag when needed, and clear .turbo cache if builds are stale.
Risk: Auth token sync between apps
Mitigation: Both apps use same Redis session store. Tokens are independent but validated against same backend.
[S10] Success Criteria
- [ ] Web app works unchanged after migration
- [ ] Admin app can login with same credentials
- [ ] Admin app can list users and view details
- [ ] Admin app can display analytics charts
- [ ] Both apps deploy independently to Cloudflare Workers
- [ ]
bun devstarts both apps concurrently - [ ]
bun buildbuilds all packages and apps - [ ] No circular dependency errors
- [ ] TypeScript type checking passes for all packages