Skip to main content

Mo - Deployment & Launch Document

1. Infrastructure Overview

┌─────────────────────────────────────────────────────────────────┐
│ VERCEL │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Next.js App │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Pages │ │ API │ │ Edge │ │ Static │ │ │
│ │ │ (SSR) │ │ Routes │ │Functions│ │ Assets │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
└──────────────────────────────┼───────────────────────────────────┘

┌──────────────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ NEON │ │ UPSTASH │ │ CLERK │
│ PostgreSQL │ │ Redis │ │ Auth │
│ (Database) │ │ (Cache) │ │ (Users) │
└──────────────┘ └──────────────┘ └──────────────┘

┌──────────────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ ANTHROPIC │ │ STRIPE │ │ RESEND │
│ Claude API │ │ (Payments) │ │ (Email) │
└──────────────┘ └──────────────┘ └──────────────┘

┌──────────────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ SENTRY │ │ POSTHOG │ │ VERCEL BLOB │
│ (Errors) │ │ (Analytics) │ │ (Storage) │
└──────────────┘ └──────────────┘ └──────────────┘

2. Service Accounts to Create

ServiceURLPurposeTier
Vercelvercel.comHosting, deploymentFree → Pro
Neonneon.techPostgreSQL databaseFree
Clerkclerk.comAuthenticationFree
Stripestripe.comPaymentsPay as you go
Anthropicanthropic.comClaude AI APIPay as you go
Upstashupstash.comRedis cacheFree
Resendresend.comTransactional emailFree
Sentrysentry.ioError trackingFree
PostHogposthog.comAnalyticsFree
GitHubgithub.comCode repositoryFree

Account Setup Order

1. GitHub        → Create repo first
2. Vercel → Connect to GitHub
3. Neon → Create database
4. Clerk → Setup auth
5. Stripe → Setup payments (can do later)
6. Anthropic → Get API key
7. Upstash → Create Redis instance
8. Resend → Setup email domain
9. Sentry → Create project
10. PostHog → Create project

3. Environment Strategy

EnvironmentURLBranchDatabasePurpose
Productionmo.fitnessmainNeon mainLive users
Preview*.vercel.appPR branchesNeon main*Review changes
Developmentlocalhost:3000localNeon branchYour machine

*Preview deployments use production DB by default. For isolated testing, use Neon branching.

Neon Database Branching

# Create a development branch
neon branches create --name dev

# Each branch is a full copy with its own connection string
# Great for testing migrations without affecting production

4. Environment Variables

Required Variables

# ============================================
# DATABASE (Neon)
# ============================================
DATABASE_URL="postgresql://user:pass@host/db?sslmode=require"

# ============================================
# AUTHENTICATION (Clerk)
# ============================================
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_live_xxx"
CLERK_SECRET_KEY="sk_live_xxx"
CLERK_WEBHOOK_SECRET="whsec_xxx"

# Clerk URLs
NEXT_PUBLIC_CLERK_SIGN_IN_URL="/login"
NEXT_PUBLIC_CLERK_SIGN_UP_URL="/signup"
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL="/dashboard"
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL="/onboarding"

# ============================================
# AI (Anthropic Claude)
# ============================================
ANTHROPIC_API_KEY="sk-ant-xxx"

# ============================================
# PAYMENTS (Stripe)
# ============================================
STRIPE_SECRET_KEY="sk_live_xxx"
STRIPE_WEBHOOK_SECRET="whsec_xxx"
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_live_xxx"
STRIPE_PRO_PRICE_ID="price_xxx"

# ============================================
# CACHE (Upstash Redis)
# ============================================
UPSTASH_REDIS_REST_URL="https://xxx.upstash.io"
UPSTASH_REDIS_REST_TOKEN="xxx"

# ============================================
# STORAGE (Vercel Blob)
# ============================================
BLOB_READ_WRITE_TOKEN="vercel_blob_xxx"

# ============================================
# EMAIL (Resend)
# ============================================
RESEND_API_KEY="re_xxx"

# ============================================
# MONITORING (Sentry)
# ============================================
SENTRY_DSN="https://[email protected]/xxx"
NEXT_PUBLIC_SENTRY_DSN="https://[email protected]/xxx"
SENTRY_AUTH_TOKEN="sntrys_xxx"
SENTRY_ORG="your-org"
SENTRY_PROJECT="mo"

# ============================================
# ANALYTICS (PostHog)
# ============================================
NEXT_PUBLIC_POSTHOG_KEY="phc_xxx"
NEXT_PUBLIC_POSTHOG_HOST="https://app.posthog.com"

# ============================================
# APP CONFIG
# ============================================
NEXT_PUBLIC_APP_URL="https://mo.fitness"
NEXT_PUBLIC_APP_NAME="Mo"

Setting Variables in Vercel

# Via CLI
vercel env add DATABASE_URL production
vercel env add DATABASE_URL preview
vercel env add DATABASE_URL development

# Or use Vercel Dashboard:
# Project Settings → Environment Variables

Local Development (.env.local)

# Copy from .env.example
cp .env.example .env.local

# Fill in your development values
# This file is gitignored

5. CI/CD Pipeline

GitHub Actions Workflow

# .github/workflows/ci.yml
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

env:
DATABASE_URL: ${{ secrets.DATABASE_URL_TEST }}

jobs:
# ==========================================
# QUALITY CHECKS
# ==========================================
quality:
name: Code Quality
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Lint
run: pnpm lint

- name: Type check
run: pnpm typecheck

- name: Format check
run: pnpm format:check

# ==========================================
# UNIT & INTEGRATION TESTS
# ==========================================
test:
name: Tests
runs-on: ubuntu-latest
needs: quality
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run tests
run: pnpm test --coverage

- name: Upload coverage
uses: codecov/codecov-action@v3
if: always()
with:
files: ./coverage/lcov.info

# ==========================================
# E2E TESTS
# ==========================================
e2e:
name: E2E Tests
runs-on: ubuntu-latest
needs: quality
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps chromium

- name: Run E2E tests
run: pnpm test:e2e
env:
PLAYWRIGHT_TEST_BASE_URL: ${{ secrets.PREVIEW_URL }}

- name: Upload test results
if: failure()
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: playwright-report/
retention-days: 7

# ==========================================
# BUILD CHECK
# ==========================================
build:
name: Build
runs-on: ubuntu-latest
needs: [test, e2e]
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm build
env:
SKIP_ENV_VALIDATION: true

Vercel Deployment (Automatic)

Vercel automatically deploys:

  • Production: On push to main
  • Preview: On every PR

No additional configuration needed - just connect GitHub repo to Vercel.


6. Database Management

Drizzle Configuration

// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
schema: './lib/db/schema.ts',
out: './lib/db/migrations',
driver: 'pg',
dbCredentials: {
connectionString: process.env.DATABASE_URL!,
},
verbose: true,
strict: true,
});

Migration Scripts

// package.json
{
"scripts": {
"db:generate": "drizzle-kit generate:pg",
"db:push": "drizzle-kit push:pg",
"db:migrate": "tsx lib/db/migrate.ts",
"db:migrate:prod": "NODE_ENV=production tsx lib/db/migrate.ts",
"db:studio": "drizzle-kit studio",
"db:seed": "tsx lib/db/seed.ts",
"db:reset": "tsx lib/db/reset.ts"
}
}

Migration Runner

// lib/db/migrate.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { migrate } from 'drizzle-orm/neon-http/migrator';
import { neon } from '@neondatabase/serverless';

const sql = neon(process.env.DATABASE_URL!);
const db = drizzle(sql);

async function main() {
console.log('Running migrations...');
await migrate(db, { migrationsFolder: './lib/db/migrations' });
console.log('Migrations complete!');
process.exit(0);
}

main().catch((err) => {
console.error('Migration failed!', err);
process.exit(1);
});

Seed Script

// lib/db/seed.ts
import { db } from './index';
import { coaches, exercises, programs } from './schema';
import { coachesData } from './data/coaches';
import { exercisesData } from './data/exercises';
import { programsData } from './data/programs';

async function seed() {
console.log('Seeding database...');

// Seed coaches
await db.insert(coaches).values(coachesData);
console.log(`✓ Seeded ${coachesData.length} coaches`);

// Seed exercises
await db.insert(exercises).values(exercisesData);
console.log(`✓ Seeded ${exercisesData.length} exercises`);

// Seed programs
await db.insert(programs).values(programsData);
console.log(`✓ Seeded programs`);

console.log('Seeding complete!');
}

seed().catch(console.error);

Migration Workflow

# Development: Make schema changes
# 1. Edit lib/db/schema.ts
# 2. Generate migration
pnpm db:generate

# 3. Review generated SQL in lib/db/migrations/
# 4. Apply to dev database
pnpm db:push

# Production: Apply migrations
# 1. Merge PR to main
# 2. Run migration before deploy
pnpm db:migrate:prod
# 3. Vercel auto-deploys

7. Monitoring & Observability

Sentry Setup

// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,

// Performance monitoring
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,

// Session replay
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,

integrations: [
new Sentry.Replay({
maskAllText: false,
blockAllMedia: false,
}),
],
});
// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
});
// sentry.edge.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
});

PostHog Analytics

// lib/analytics/posthog.ts
import posthog from 'posthog-js';

export function initPostHog() {
if (typeof window !== 'undefined' && process.env.NEXT_PUBLIC_POSTHOG_KEY) {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
capture_pageview: false, // We capture manually for SPA
capture_pageleave: true,
});
}
}

// Track events
export function trackEvent(event: string, properties?: Record<string, any>) {
posthog.capture(event, properties);
}

// Track page views
export function trackPageView(url: string) {
posthog.capture('$pageview', { $current_url: url });
}

// Identify users
export function identifyUser(userId: string, traits?: Record<string, any>) {
posthog.identify(userId, traits);
}

Health Check Endpoint

// app/api/health/route.ts
import { db } from '@/lib/db';
import { redis } from '@/lib/redis';
import { NextResponse } from 'next/server';

export const runtime = 'edge';

export async function GET() {
const health: {
status: 'ok' | 'degraded' | 'down';
timestamp: string;
version: string;
checks: Record<string, 'ok' | 'error'>;
} = {
status: 'ok',
timestamp: new Date().toISOString(),
version: process.env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) || 'dev',
checks: {},
};

// Check database
try {
await db.execute('SELECT 1');
health.checks.database = 'ok';
} catch {
health.checks.database = 'error';
health.status = 'degraded';
}

// Check Redis
try {
await redis.ping();
health.checks.redis = 'ok';
} catch {
health.checks.redis = 'error';
health.status = 'degraded';
}

const statusCode = health.status === 'ok' ? 200 : 503;
return NextResponse.json(health, { status: statusCode });
}

Alert Configuration

AlertConditionAction
Error rate spike> 10 errors/minEmail notification
P95 latency> 2000msEmail notification
Database errorsAny connection failureEmail + SMS
Payment failuresAny Stripe webhook errorEmail + SMS
Auth failures> 5 failed logins/user/hourEmail
Health check fail2 consecutive failuresEmail + SMS

8. Security Configuration

Security Headers (next.config.js)

// next.config.js
const securityHeaders = [
{
key: 'X-DNS-Prefetch-Control',
value: 'on',
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
];

/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: securityHeaders,
},
];
},
};

module.exports = nextConfig;

Rate Limiting (Upstash)

// lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});

// General API rate limit
export const apiRateLimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(100, '1 m'), // 100 requests per minute
analytics: true,
});

// Auth rate limit (stricter)
export const authRateLimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, '1 m'), // 10 requests per minute
analytics: true,
});

// AI chat rate limit
export const chatRateLimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(30, '1 m'), // 30 messages per minute
analytics: true,
});

Input Validation (Zod)

// lib/validations/workout.ts
import { z } from 'zod';

export const logSetSchema = z.object({
exerciseId: z.string().uuid(),
setNumber: z.number().int().min(1).max(20),
weight: z.number().min(0).max(2000),
reps: z.number().int().min(0).max(100),
rpe: z.number().min(1).max(10).optional(),
notes: z.string().max(500).optional(),
});

export const startWorkoutSchema = z.object({
programDayId: z.string().uuid(),
date: z.string().datetime().optional(),
});

Security Checklist

Authentication:
□ Clerk handles all auth (OAuth, sessions, JWT)
□ Webhook signatures validated
□ Session expiration configured
□ OAuth state validation (Clerk handles)

API Security:
□ Rate limiting on all endpoints
□ Input validation with Zod
□ SQL injection prevented (Drizzle ORM)
□ CORS configured for domain only
□ API routes check authentication

Data Security:
□ HTTPS enforced (Vercel)
□ Database encrypted at rest (Neon)
□ Secrets in environment variables only
□ No sensitive data in logs
□ PII handling compliant

Headers:
□ HSTS enabled
□ X-Frame-Options set
□ X-Content-Type-Options set
□ CSP configured
□ Referrer-Policy set

Payments:
□ Stripe webhook signature validation
□ Idempotency keys for charges
□ No card data touches our servers

Privacy:
□ GDPR data export available
□ Account deletion available
□ Privacy policy accessible
□ Cookie consent (if needed)

9. Performance Optimization

Next.js Optimizations

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// Image optimization
images: {
remotePatterns: [
{ hostname: 'img.clerk.com' },
{ hostname: '*.vercel-storage.com' },
],
formats: ['image/avif', 'image/webp'],
},

// Experimental features
experimental: {
optimizeCss: true,
},

// Compression
compress: true,

// Power optimizations
poweredByHeader: false,
};

module.exports = nextConfig;

Bundle Analysis

// package.json
{
"scripts": {
"analyze": "ANALYZE=true pnpm build"
}
}
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});

module.exports = withBundleAnalyzer(nextConfig);

Performance Targets

MetricTargetMeasurement
Lighthouse Performance> 90Lighthouse CI
Lighthouse Accessibility> 90Lighthouse CI
Lighthouse Best Practices> 90Lighthouse CI
Lighthouse SEO> 90Lighthouse CI
Lighthouse PWA> 90Lighthouse CI
First Contentful Paint< 1.5sWeb Vitals
Largest Contentful Paint< 2.5sWeb Vitals
Cumulative Layout Shift< 0.1Web Vitals
First Input Delay< 100msWeb Vitals
Time to First Byte< 200msVercel Analytics
API p95 Response Time< 200msCustom logging

Caching Strategy

// API route caching
export const revalidate = 60; // Revalidate every 60 seconds

// Static page caching
export const dynamic = 'force-static';

// Dynamic with ISR
export const revalidate = 3600; // Revalidate every hour

10. Domain & DNS Setup

Domain Configuration

RecordTypeNameValueTTL
A@76.76.21.21300
CNAMEwwwcname.vercel-dns.com300
TXT@vercel-verification=xxx300
MX@feedback-smtp.us-east-1.amazonses.com300
TXT@v=spf1 include:amazonses.com ~all300

SSL Certificate

Vercel automatically provisions and renews SSL certificates via Let's Encrypt.

Vercel Domain Setup

# Add domain via CLI
vercel domains add mo.fitness

# Or via Dashboard:
# Project Settings → Domains → Add

11. Backup Strategy

Database Backups (Neon)

FeatureFree TierPro Tier
Point-in-time recovery7 days30 days
BranchingUnlimitedUnlimited
ExportManualManual

Manual Backup Script

#!/bin/bash
# scripts/backup-db.sh

DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_${DATE}.sql"

# Export database
pg_dump $DATABASE_URL > $BACKUP_FILE

# Compress
gzip $BACKUP_FILE

# Upload to storage (optional)
# aws s3 cp ${BACKUP_FILE}.gz s3://mo-backups/

User Data Export

// app/api/v1/users/me/export/route.ts
export async function GET(request: Request) {
const user = await getCurrentUser();

// Gather all user data
const userData = {
profile: await getUserProfile(user.id),
workouts: await getUserWorkouts(user.id),
weightEntries: await getUserWeightEntries(user.id),
personalRecords: await getUserPRs(user.id),
chatHistory: await getUserChatHistory(user.id),
exportedAt: new Date().toISOString(),
};

return new Response(JSON.stringify(userData, null, 2), {
headers: {
'Content-Type': 'application/json',
'Content-Disposition': `attachment; filename="mo-data-export-${user.id}.json"`,
},
});
}

12. Rollback Procedures

Vercel Instant Rollback

# Via CLI
vercel rollback

# Via Dashboard
# Deployments → Select previous deployment → "..." → Promote to Production

Database Rollback

# Option 1: Point-in-time recovery (Neon Dashboard)
# Restore to specific timestamp

# Option 2: Restore from branch
neon branches restore main --source dev

# Option 3: Run down migration
pnpm db:migrate:down

Feature Flags (Emergency Kill Switch)

// lib/feature-flags.ts
import { PostHog } from 'posthog-node';

const posthog = new PostHog(process.env.POSTHOG_API_KEY!);

export async function isFeatureEnabled(
feature: string,
userId: string
): Promise<boolean> {
return await posthog.isFeatureEnabled(feature, userId);
}

// Usage in code
if (await isFeatureEnabled('ai-coach', user.id)) {
// Show AI coach
}

Rollback Decision Matrix

SeveritySymptomsAction
P0 CriticalApp down, data loss riskImmediate rollback
P1 HighMajor feature brokenRollback within 1 hour
P2 MediumMinor feature brokenHotfix or rollback
P3 LowUI issue, no data impactFix forward

13. Launch Checklist

Week Before Launch

Infrastructure:
□ All environment variables set in Vercel
□ Custom domain configured and SSL active
□ Database migrations run on production
□ Seed data loaded (coaches, exercises, programs)
□ Redis cache warmed up

Testing:
□ All E2E tests passing
□ Manual testing complete on production
□ Performance audit passed (Lighthouse > 90)
□ Security audit complete
□ Load testing done (100 concurrent users)

Integrations:
□ Stripe production mode enabled
□ Stripe webhook endpoint configured
□ Clerk production instance
□ OAuth providers configured (Google, Apple)
□ Email sending verified (Resend)

Monitoring:
□ Sentry connected and receiving errors
□ PostHog tracking events
□ Vercel Analytics enabled
□ Health check endpoint responding
□ Alert notifications configured

Legal:
□ Privacy Policy page live
□ Terms of Service page live
□ Cookie consent (if required)

Content:
□ All copy reviewed
□ Social preview images (og:image)
□ Favicon and app icons
□ 404 page styled
□ Error pages styled

Day Before Launch

□ Final smoke test on production
□ Test complete user flow:
□ Visit landing page
□ Sign up with email
□ Sign up with Google
□ Complete onboarding
□ Start a workout
□ Log sets
□ Complete workout
□ Log weight
□ Chat with coach
□ Upgrade to Pro
□ Cancel subscription
□ Verify Stripe webhooks receiving
□ Check all monitoring dashboards
□ Prepare rollback plan
□ Brief support plan (email ready)
□ Draft social media announcements
□ Get good sleep!

Launch Day

Hour 0:
□ Final health check
□ Remove any beta banners
□ Verify production is stable

Hour 1:
□ Announce on social media
□ Send to initial users/testers
□ Monitor Sentry for errors
□ Monitor Vercel for performance

Hours 2-8:
□ Monitor error rates closely
□ Monitor server performance
□ Respond to any issues immediately
□ Collect early feedback

End of Day:
□ Review metrics
□ Fix any critical issues
□ Plan next day priorities
□ Celebrate! 🎉

Post-Launch (24-48 hours)

□ Monitor error rates trend
□ Check user signup numbers
□ Review any support requests
□ Fix critical bugs immediately
□ Collect and organize feedback
□ Thank early users
□ Plan first iteration

14. Scaling Considerations

Service Limits (Free Tiers)

ServiceFree LimitUpgrade Trigger
Vercel100GB bandwidth> 1,000 daily users
Neon0.5GB storage, 190 compute hours> 500 active users
Clerk10,000 MAU> 5,000 users
Upstash10,000 requests/day> 1,000 daily active
Resend3,000 emails/month> 2,000 users
Sentry5,000 errors/monthHigh error rate
PostHog1M events/month> 5,000 users

Cost Estimates at Scale

UsersVercelNeonClerkAnthropicTotal/mo
100FreeFreeFree~$10~$10
1,000FreeFreeFree~$100~$100
10,000$20$19$25~$500~$564
50,000$20$69$99~$2,000~$2,188

*Anthropic costs depend heavily on chat usage. Optimize prompts early.

Optimization Strategies

1. Prompt Optimization
- Shorter system prompts
- Cache common responses
- Limit conversation history

2. Database Optimization
- Add indexes as queries grow
- Archive old data
- Use connection pooling

3. Caching
- Redis for frequent queries
- Static generation where possible
- CDN for assets

4. Code Splitting
- Lazy load heavy components
- Dynamic imports for 3D
- Route-based splitting

15. Support & Maintenance

Support Channels

ChannelToolResponse Time
Email[email protected]< 24 hours
In-app feedbackPostHog surveys< 48 hours
Twitter@mo_fitness< 12 hours
Help docs/help pageSelf-service

Maintenance Windows

Scheduled Maintenance:
- Time: Tuesdays 2-4 AM EST
- Frequency: As needed (aim for < 1/month)
- Notice: 48 hours minimum
- Status page: status.mo.fitness (optional)

On-Call Rotation

For solo development:
- Monitor Sentry/Vercel notifications
- Set up PagerDuty or similar for P0 alerts
- Define "office hours" for non-critical issues
- Have rollback plan ready

Document Status

  • Infrastructure Overview
  • Service Accounts
  • Environment Strategy
  • Environment Variables
  • CI/CD Pipeline
  • Database Management
  • Monitoring & Observability
  • Security Configuration
  • Performance Optimization
  • Domain & DNS Setup
  • Backup Strategy
  • Rollback Procedures
  • Launch Checklist
  • Scaling Considerations
  • Support & Maintenance

Phase 6: Deployment & Launch - COMPLETE


Document created: December 2024 Version: 1.0 Status: Complete