Version: 1.0
Date: January 2025
Status: In Progress
This guide documents the migration of BlackPill from a Flutter + Express.js architecture to React Native/Expo + Next.js API routes, matching SmileScore's unified structure.
- Architecture Comparison
- Migration Phases
- Feature Mapping
- API Endpoint Migration
- Environment Variables
- Deployment Configuration
- Testing Strategy
- Rollback Plan
BlackPill/
├── mobile/ # Flutter (Dart)
│ ├── lib/
│ │ ├── features/ # 23+ feature modules
│ │ ├── core/ # Core utilities
│ │ └── config/ # Configuration
│ ├── android/ # Android native code
│ ├── ios/ # iOS native code
│ └── pubspec.yaml # Flutter dependencies
├── backend/ # Express.js API (separate Vercel project)
│ ├── api/ # 50+ API endpoints
│ ├── utils/ # Business logic
│ ├── middleware/ # Auth, rate limiting
│ └── vercel.json # Backend deployment config
├── web/ # Next.js Pages Router (creator dashboard)
│ ├── src/
│ │ ├── pages/ # Pages Router pages
│ │ └── components/ # React components
│ └── vercel.json # Web deployment config
└── supabase/ # Database migrations
Issues:
- Two separate Vercel projects (backend + web)
- Flutter mobile app cannot share code with web
- Express.js backend requires separate deployment
- No unified build process
BlackPill/
├── mobile/ # React Native/Expo (TypeScript)
│ ├── app/ # Expo Router app directory
│ ├── components/ # Reusable UI components
│ ├── lib/ # Business logic, API clients
│ ├── screens/ # Screen components
│ ├── app.json # Expo configuration
│ └── package.json # React Native dependencies
├── web/ # Next.js App Router + APIs
│ ├── app/
│ │ ├── api/ # All API routes (migrated from backend/)
│ │ ├── (marketing)/ # Landing, pricing pages
│ │ ├── dashboard/ # User dashboard
│ │ └── app/ # Expo web static files
│ ├── scripts/
│ │ └── build-expo-web.js # Expo web build script
│ └── package.json
├── supabase/ # Database migrations (unchanged)
├── docs/
│ ├── PRD.md
│ └── MIGRATION_GUIDE.md
└── vercel.json # Single Vercel project config
Benefits:
- Single Vercel project deployment
- Code sharing between mobile and web
- Unified build process
- Next.js API routes (serverless functions)
- Expo web app served from same domain
Status: Complete
- Create migration guide document
- Document current architecture
- Plan new structure
Status: ✅ Complete
Steps:
- Create new
mobile/directory - Initialize Expo project:
npx create-expo-app@latest mobile --template blank-typescript - Configure
app.jsonfor iOS, Android, and Web - Set up TypeScript configuration
- Install core dependencies
Dependencies to Install:
{
"@expo/vector-icons": "^14.0.0",
"@react-navigation/native": "^7.1.20",
"@react-navigation/native-stack": "^7.6.3",
"@supabase/supabase-js": "^2.83.0",
"expo": "~54.0.25",
"expo-camera": "~17.0.9",
"expo-image-picker": "~17.0.8",
"expo-notifications": "~0.32.13",
"expo-secure-store": "~15.0.7",
"expo-web-browser": "~15.0.9",
"react": "19.1.0",
"react-native": "0.81.5",
"react-native-purchases": "^8.2.0"
}Auth System:
- Port Supabase auth from Flutter (
mobile/lib/config/supabase_client.dart) - Use
@supabase/supabase-jswithexpo-secure-storefor token storage - Implement Google OAuth via Supabase Auth
State Management:
- Replace Riverpod with React Context + hooks
- Create auth context, user context, subscription context
- Use React Query for API data fetching
Navigation:
- Replace go_router with Expo Router
- Set up file-based routing in
mobile/app/ - Configure deep linking for referral codes
Theme System:
- Create theme configuration matching Flutter design
- Colors: Deep Black (#0F0F1E), Pink (#FF0080), Cyan (#00D9FF), Purple (#B700FF)
- Typography: Inter font family
- Component styles: Glass cards, gradient buttons
Authentication & Onboarding (4 screens):
-
screens/SplashScreen.tsx- App splash with logo -
screens/LoginScreen.tsx- Email/password + Google OAuth -
screens/SignupScreen.tsx- Registration with age verification -
screens/OnboardingScreen.tsx- Permissions and disclaimers
Core Analysis (3 screens):
-
screens/HomeScreen.tsx- Main dashboard with bottom tabs -
screens/CameraScreen.tsx- Photo capture with quality checks -
screens/AnalysisResultScreen.tsx- Score display with 6-dimension breakdown
Progress & History (3 screens):
-
screens/HistoryScreen.tsx- Photo gallery with timeline -
screens/ComparisonScreen.tsx- Before/after comparison -
screens/ProgressScreen.tsx- Charts and analytics
Routines & Habits (3 screens):
-
screens/RoutinesScreen.tsx- Routine list -
screens/RoutineDetailScreen.tsx- Routine tasks and schedule -
screens/TasksScreen.tsx- Daily checklist
Social & Gamification (3 screens):
-
screens/LeaderboardScreen.tsx- Rankings and filters -
screens/AchievementsScreen.tsx- Badge collection -
screens/ShareScreen.tsx- Share card generation
Challenges & Wellness (3 screens):
-
screens/ChallengesScreen.tsx- Available challenges -
screens/ChallengeDetailScreen.tsx- Challenge progress -
screens/WellnessScreen.tsx- Health data integration
Settings & Profile (4 screens):
-
screens/ProfileScreen.tsx- User profile -
screens/SettingsScreen.tsx- App settings -
screens/SubscriptionScreen.tsx- Subscription management -
screens/EthicalSettingsScreen.tsx- Privacy and wellness controls
AI Coach (1 screen):
-
screens/AICoachScreen.tsx- Chat interface with conversation history
Total: 24 screens migrated ✅
Status: ✅ Complete
| Express.js Route | Next.js Route | Status |
|---|---|---|
backend/api/analyze/index.js |
web/app/api/analyze/route.ts |
⏳ |
backend/api/auth/me.js |
web/app/api/auth/me/route.ts |
⏳ |
backend/api/subscriptions/create-checkout.js |
web/app/api/subscriptions/create-checkout/route.ts |
⏳ |
backend/api/subscriptions/status.js |
web/app/api/subscriptions/status/route.ts |
⏳ |
backend/api/subscriptions/cancel.js |
web/app/api/subscriptions/cancel/route.ts |
⏳ |
backend/api/routines/list.js |
web/app/api/routines/list/route.ts |
⏳ |
backend/api/routines/generate.js |
web/app/api/routines/generate/route.ts |
⏳ |
backend/api/routines/complete-task.js |
web/app/api/routines/complete-task/route.ts |
⏳ |
backend/api/routines/today.js |
web/app/api/routines/today/route.ts |
⏳ |
backend/api/routines/stats.js |
web/app/api/routines/stats/route.ts |
⏳ |
backend/api/routines/update.js |
web/app/api/routines/update/route.ts |
⏳ |
backend/api/routines/delete.js |
web/app/api/routines/delete/route.ts |
⏳ |
backend/api/routines/tasks.js |
web/app/api/routines/tasks/route.ts |
⏳ |
backend/api/analyses/index.js |
web/app/api/analyses/route.ts |
⏳ |
backend/api/analyses/history.js |
web/app/api/analyses/history/route.ts |
⏳ |
backend/api/analyses/[id].js |
web/app/api/analyses/[id]/route.ts |
⏳ |
backend/api/referral/accept.js |
web/app/api/referral/accept/route.ts |
⏳ |
backend/api/referral/stats.js |
web/app/api/referral/stats/route.ts |
⏳ |
backend/api/share/generate-card.js |
web/app/api/share/generate-card/route.ts |
⏳ |
backend/api/leaderboard/index.js |
web/app/api/leaderboard/route.ts |
⏳ |
backend/api/leaderboard/referrals.js |
web/app/api/leaderboard/referrals/route.ts |
⏳ |
backend/api/achievements/list.js |
web/app/api/achievements/list/route.ts |
⏳ |
backend/api/achievements/unlock.js |
web/app/api/achievements/unlock/route.ts |
⏳ |
backend/api/challenges/list.js |
web/app/api/challenges/list/route.ts |
⏳ |
backend/api/challenges/join.js |
web/app/api/challenges/join/route.ts |
⏳ |
backend/api/challenges/my-challenges.js |
web/app/api/challenges/my-challenges/route.ts |
⏳ |
backend/api/challenges/checkin.js |
web/app/api/challenges/checkin/route.ts |
⏳ |
backend/api/checkins/checkin.js |
web/app/api/checkins/checkin/route.ts |
⏳ |
backend/api/checkins/status.js |
web/app/api/checkins/status/route.ts |
⏳ |
backend/api/comparisons/compare.js |
web/app/api/comparisons/compare/route.ts |
⏳ |
backend/api/community/public-analyses.js |
web/app/api/community/public-analyses/route.ts |
⏳ |
backend/api/community/comments.js |
web/app/api/community/comments/route.ts |
⏳ |
backend/api/community/vote.js |
web/app/api/community/vote/route.ts |
⏳ |
backend/api/creators/apply.js |
web/app/api/creators/apply/route.ts |
⏳ |
backend/api/creators/dashboard.js |
web/app/api/creators/dashboard/route.ts |
⏳ |
backend/api/creators/performance.js |
web/app/api/creators/performance/route.ts |
⏳ |
backend/api/creators/coupons.js |
web/app/api/creators/coupons/route.ts |
⏳ |
backend/api/ai-coach/chat.js |
web/app/api/ai-coach/chat/route.ts |
⏳ |
backend/api/ai-coach/conversations.js |
web/app/api/ai-coach/conversations/route.ts |
⏳ |
backend/api/ai-coach/messages.js |
web/app/api/ai-coach/messages/route.ts |
⏳ |
backend/api/goals/create.js |
web/app/api/goals/create/route.ts |
⏳ |
backend/api/goals/list.js |
web/app/api/goals/list/route.ts |
⏳ |
backend/api/goals/update-progress.js |
web/app/api/goals/update-progress/route.ts |
⏳ |
backend/api/insights/generate.js |
web/app/api/insights/generate/route.ts |
⏳ |
backend/api/insights/list.js |
web/app/api/insights/list/route.ts |
⏳ |
backend/api/insights/mark-viewed.js |
web/app/api/insights/mark-viewed/route.ts |
⏳ |
backend/api/products/list.js |
web/app/api/products/list/route.ts |
⏳ |
backend/api/products/recommend.js |
web/app/api/products/recommend/route.ts |
⏳ |
backend/api/products/click.js |
web/app/api/products/click/route.ts |
⏳ |
backend/api/scoring/methodology.js |
web/app/api/scoring/methodology/route.ts |
⏳ |
backend/api/scoring/preferences.js |
web/app/api/scoring/preferences/route.ts |
⏳ |
backend/api/scoring/recalculate.js |
web/app/api/scoring/recalculate/route.ts |
⏳ |
backend/api/ethical/settings.js |
web/app/api/ethical/settings/route.ts |
⏳ |
backend/api/ethical/acknowledge-disclaimers.js |
web/app/api/ethical/acknowledge-disclaimers/route.ts |
⏳ |
backend/api/ethical/wellness-check.js |
web/app/api/ethical/wellness-check/route.ts |
⏳ |
backend/api/ethical/resources.js |
web/app/api/ethical/resources/route.ts |
⏳ |
backend/api/wellness/data.js |
web/app/api/wellness/data/route.ts |
⏳ |
backend/api/wellness/sync.js |
web/app/api/wellness/sync/route.ts |
⏳ |
backend/api/wellness/correlations.js |
web/app/api/wellness/correlations/route.ts |
⏳ |
backend/api/user/export.js |
web/app/api/user/export/route.ts |
⏳ |
backend/api/user/push-token.js |
web/app/api/user/push-token/route.ts |
⏳ |
backend/api/admin/review-queue.js |
web/app/api/admin/review-queue/route.ts |
⏳ |
backend/api/admin/review-action.js |
web/app/api/admin/review-action/route.ts |
⏳ |
backend/api/webhooks/stripe.js |
web/app/api/webhooks/stripe/route.ts |
⏳ |
backend/api/cron/check-renewals.js |
web/app/api/cron/check-renewals/route.ts |
⏳ |
backend/api/cron/recalculate-leaderboard.js |
web/app/api/cron/recalculate-leaderboard/route.ts |
⏳ |
Total: 60+ endpoints to migrate
Utils to Migrate:
backend/utils/openai-client.js→web/lib/openai/client.tsbackend/utils/google-vision.js→web/lib/vision/client.tsbackend/utils/supabase.js→web/lib/supabase/client.tsbackend/utils/photo-verification.js→web/lib/vision/photo-verification.tsbackend/utils/moderation.js→web/lib/moderation/client.tsbackend/utils/share-card-generator.js→web/lib/share-card/generator.tsbackend/utils/push-notification-service.js→web/lib/notifications/push.tsbackend/utils/email-service.js→web/lib/emails/service.tsbackend/utils/cache.js→web/lib/cache/client.tsbackend/utils/fallback-scoring.js→web/lib/scoring/fallback.ts
Middleware to Migrate:
backend/middleware/auth.js→web/lib/auth/middleware.tsbackend/middleware/rate-limit.js→web/lib/rate-limit/middleware.tsbackend/middleware/error-handler.js→web/lib/errors/handler.tsbackend/middleware/request-id.js→web/lib/middleware/request-id.ts
Webhooks:
- Stripe webhook handler →
web/app/api/webhooks/stripe/route.ts - Use Vercel's webhook signature verification
Cron Jobs:
- Move to
vercel.jsoncrons configuration check-renewals→ Daily at midnight UTCrecalculate-leaderboard→ Weekly on Sunday at midnight UTC
Status: ✅ Complete
Current Structure (Pages Router):
web/src/
├── pages/
│ ├── index.tsx
│ ├── pricing.tsx
│ └── dashboard/
└── components/
Target Structure (App Router):
web/app/
├── page.tsx # Landing page
├── pricing/
│ └── page.tsx
├── dashboard/
│ └── page.tsx
└── api/ # API routes
Migration Steps:
- Create
web/app/directory - Move pages to App Router structure
- Update routing logic
- Migrate layouts and metadata
- Update links and navigation
Create web/scripts/build-expo-web.js:
- Builds Expo web app from
mobile/directory - Outputs static files to
web/public/app/ - Runs during Vercel build process
Update web/package.json:
{
"scripts": {
"build": "npm run build:expo && next build",
"build:expo": "node scripts/build-expo-web.js"
}
}Create Next.js route handler for /app:
- Serves Expo web static files
- Handles client-side routing
Pages to Create/Update:
- Landing page (
web/app/page.tsx) - Pricing page (
web/app/pricing/page.tsx) - "Try Web App" button →
/approute - Creator dashboard (existing)
- Auth pages (login, signup)
Status: ✅ Complete
Create root vercel.json:
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"buildCommand": "cd mobile && npm install && cd ../web && npm install && npm run build",
"outputDirectory": "web/.next",
"installCommand": "echo 'Dependencies installed in buildCommand'",
"crons": [
{
"path": "/api/cron/check-renewals",
"schedule": "0 0 * * *"
},
{
"path": "/api/cron/recalculate-leaderboard",
"schedule": "0 0 * * 0"
}
]
}Consolidate from both projects:
From backend/.env:
SUPABASE_URLSUPABASE_SERVICE_ROLE_KEYOPENAI_API_KEYGOOGLE_CLOUD_VISION_API_KEYSTRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRETNEXT_PUBLIC_VAPID_PUBLIC_KEY(for web push)VAPID_PRIVATE_KEY(for web push)POSTHOG_API_KEYREDIS_URLRESEND_API_KEY
From mobile/.env:
EXPO_PUBLIC_SUPABASE_URLEXPO_PUBLIC_SUPABASE_ANON_KEYEXPO_PUBLIC_APP_URLEXPO_PUBLIC_POSTHOG_KEY
New unified .env structure:
# Supabase
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
# Expo Mobile
EXPO_PUBLIC_SUPABASE_URL=
EXPO_PUBLIC_SUPABASE_ANON_KEY=
EXPO_PUBLIC_APP_URL=
# OpenAI
OPENAI_API_KEY=
# Google Cloud Vision
GOOGLE_CLOUD_VISION_API_KEY=
GOOGLE_CLOUD_VISION_PROJECT_ID=
# Stripe
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
# Push Notifications (VAPID for Web Push)
# Generate with: npx web-push generate-vapid-keys
NEXT_PUBLIC_VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
# Expo Push API works automatically - no keys needed!
# Analytics
POSTHOG_API_KEY=
NEXT_PUBLIC_POSTHOG_KEY=
EXPO_PUBLIC_POSTHOG_KEY=
POSTHOG_HOST=https://app.posthog.com
# Redis
REDIS_URL=
# Email
RESEND_API_KEY=
RESEND_FROM_EMAIL=
# App URLs
NEXT_PUBLIC_APP_URL=
NEXT_PUBLIC_APP_NAME=
EXPO_PUBLIC_APP_URL=
EXPO_PUBLIC_APP_NAME=
EXPO_PUBLIC_PROJECT_ID=After migration is complete and tested:
- ✅ Delete
backend/folder - COMPLETED - ✅ Delete
backend/vercel.json- COMPLETED - ✅ Delete
web/vercel.json(if separate) - COMPLETED - ✅ Archive Flutter
mobile/contents - COMPLETED (all Dart files removed) - ✅ Update
.gitignore- COMPLETED
Status: ✅ Complete
- Created unified environment variable documentation (
docs/ENVIRONMENT_SETUP.md) - Updated
.gitignoreto exclude all.envfiles - Documented all required variables for root, web, and mobile
Status: ✅ Complete
- Updated root
package.jsonto remove backend workspace - Improved Expo web build script with better error handling
- Updated
vercel.jsonwith proper function configuration - Added build scripts for development workflow
| Flutter Feature | React Native Equivalent | Notes |
|---|---|---|
flutter_riverpod |
React Context + hooks | State management |
go_router |
Expo Router | Navigation |
supabase_flutter |
@supabase/supabase-js |
Auth & database |
image_picker |
expo-image-picker |
Photo selection |
camera |
expo-camera |
Camera access |
flutter_secure_storage |
expo-secure-store |
Secure token storage |
firebase_messaging |
expo-notifications + Expo Push API |
Push notifications (no Firebase needed) |
flutter_stripe |
react-native-purchases |
Subscriptions (RevenueCat) |
share_plus |
expo-sharing |
Share functionality |
qr_flutter |
react-native-qrcode-svg |
QR code generation |
fl_chart |
react-native-chart-kit or victory-native |
Charts |
health |
expo-health or react-native-health |
Health data |
confetti |
react-native-confetti-cannon |
Animations |
google_fonts |
expo-google-fonts |
Typography |
Flutter (Riverpod):
final userProvider = StateNotifierProvider<UserNotifier, User?>((ref) {
return UserNotifier();
});React Native (Context + Hooks):
const UserContext = createContext<User | null>(null);
export const useUser = () => {
const context = useContext(UserContext);
return context;
};Express.js Format:
// Request
app.get('/api/analyses', async (req, res) => {
const analyses = await getAnalyses(req.user.id);
res.json({ analyses });
});
// Response
{
"analyses": [...]
}Next.js API Route Format:
// Request
export async function GET(request: Request) {
const user = await getAuthenticatedUser(request);
const analyses = await getAnalyses(user.id);
return Response.json({ analyses });
}
// Response
{
"analyses": [...]
}Express.js:
const authMiddleware = async (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
const user = await verifyToken(token);
req.user = user;
next();
};Next.js:
export async function getAuthenticatedUser(request: Request) {
const authHeader = request.headers.get('authorization');
const token = authHeader?.split(' ')[1];
const user = await verifyToken(token);
return user;
}Express.js:
try {
// logic
} catch (error) {
res.status(500).json({ error: error.message });
}Next.js:
try {
// logic
} catch (error) {
return Response.json(
{ error: error.message },
{ status: 500 }
);
}- Copy
.env.exampleto.env.local - Fill in all required variables
- For mobile: Copy to
mobile/.env.local - For web: Variables auto-loaded by Next.js
- Add all variables to Vercel project settings
- Ensure
EXPO_PUBLIC_*variables are set for mobile builds - Ensure
NEXT_PUBLIC_*variables are set for web builds - Test environment variable access in both mobile and web
-
Create/Update Vercel Project:
- Single project for entire monorepo
- Root directory:
.(root of repo) - Build command: Auto-detected from
vercel.json - Output directory:
web/.next
-
Environment Variables:
- Add all variables from consolidated list
- Mark
EXPO_PUBLIC_*as available to mobile builds - Mark
NEXT_PUBLIC_*as available to web builds
-
Cron Jobs:
- Configure in
vercel.json - Test cron endpoints after deployment
- Configure in
-
Mobile Build:
cd mobile && npm install- Builds Expo web app:
expo export --platform web - Outputs to
web/public/app/
-
Web Build:
cd web && npm install- Runs Expo web build script
- Builds Next.js:
next build - Outputs to
web/.next
-
Deployment:
- Vercel deploys
web/.nextdirectory - Serves static files from
web/public/ - API routes from
web/app/api/
- Vercel deploys
-
Unit Tests:
- Test utilities and helpers
- Test API clients
- Test state management
-
Integration Tests:
- Test navigation flows
- Test auth flows
- Test API integration
-
E2E Tests:
- Test critical user journeys
- Test subscription flow
- Test analysis flow
-
Unit Tests:
- Test individual API routes
- Test middleware functions
- Test utility functions
-
Integration Tests:
- Test API + database integration
- Test webhook handlers
- Test cron jobs
-
Component Tests:
- Test React components
- Test page components
- Test API route handlers
-
E2E Tests:
- Test marketing pages
- Test dashboard flows
- Test Expo web app integration
-
Keep Old Structure:
- Don't delete
backend/or Fluttermobile/until migration is proven stable - Keep both Vercel projects active during transition
- Don't delete
-
Feature Flags:
- Use feature flags to toggle between old and new implementations
- Gradually migrate users to new system
-
Database:
- No database schema changes required
- Both old and new APIs use same Supabase database
-
Rollback Steps:
- Revert code changes
- Switch Vercel project back to old structure
- Restore environment variables
- Test critical flows
- Backup current codebase
- Document all environment variables
- List all API endpoints
- List all Flutter screens
- Create migration branch
- Create migration guide
- Set up new project structure
- Initialize Expo project
- Set up Next.js App Router
- Initialize Expo project
- Set up core infrastructure
- Migrate auth screens
- Migrate analysis screens
- Migrate progress screens
- Migrate routine screens
- Migrate social screens
- Migrate settings screens
- Test mobile app
- Migrate API endpoints (60+)
- Migrate business logic
- Migrate middleware
- Migrate webhooks
- Migrate cron jobs
- Test API endpoints
- Upgrade to App Router
- Integrate Expo web build
- Update marketing pages
- Test web app
- Create single Vercel config
- Consolidate environment variables
- Update .gitignore
- Update root package.json
- Improve build script
- Deploy to staging
- Test all flows
- Deploy to production
- Remove old structure
- Expo Documentation
- React Native Documentation
- Next.js App Router Documentation
- Supabase JavaScript Client
- Vercel Deployment Documentation
For questions or issues during migration:
- Check this guide first
- Review SmileScore implementation for reference
- Consult PRD.md for feature specifications
- Test incrementally and document issues
Last Updated: January 2025
Next Review: After Phase 2 completion