Everything you need to set up, customize, and deploy your SaaS application.
This project is a Turborepo monorepo with pnpm workspaces. It contains two applications and shared packages.
wellness/
├── apps/
│ ├── web/ # Next.js web application
│ │ ├── src/app/ # App Router pages
│ │ ├── src/components/ # React components (shadcn/ui)
│ │ └── src/lib/ # Utilities and auth client
│ └── native/ # Expo React Native app
│ ├── app/ # Expo Router screens
│ ├── components/ # Native components (HeroUI Native)
│ └── contexts/ # React contexts
├── packages/
│ ├── backend/ # Convex backend (shared)
│ │ └── convex/ # Convex functions, schema, HTTP
│ └── i18n/ # Shared translations
│ └── messages/ # Translation JSON files
├── turbo.json # Turborepo config
└── pnpm-workspace.yaml # Workspace configgit clone <your-repo-url> wellness
cd wellness
pnpm installSet these on your Convex deployment using the CLI. These are stored securely on Convex servers.
# Generate and set auth secret
npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
# Site URLs
npx convex env set SITE_URL http://localhost:3004
npx convex env set NATIVE_APP_URL wellness://
# Google OAuth (from Google Cloud Console)
npx convex env set GOOGLE_CLIENT_ID your_client_id
npx convex env set GOOGLE_CLIENT_SECRET your_client_secret
# Polar Payments (from polar.sh dashboard)
npx convex env set POLAR_ORGANIZATION_TOKEN your_token
npx convex env set POLAR_WEBHOOK_SECRET your_webhook_secret
# AI Gateway (from Vercel dashboard)
npx convex env set AI_GATEWAY_API_KEY your_api_key
# Cloudflare R2 Storage
npx convex env set R2_TOKEN your_token
npx convex env set R2_ACCESS_KEY_ID your_key
npx convex env set R2_SECRET_ACCESS_KEY your_secret
npx convex env set R2_ENDPOINT your_endpoint
npx convex env set R2_BUCKET your_bucket# apps/web/.env.local
NEXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud
NEXT_PUBLIC_CONVEX_SITE_URL=https://your-deployment.convex.site# apps/native/.env.local
EXPO_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud
EXPO_PUBLIC_CONVEX_SITE_URL=https://your-deployment.convex.site
EXPO_PUBLIC_REVENUECAT_IOS_API_KEY=your_key
EXPO_PUBLIC_REVENUECAT_ANDROID_API_KEY=your_key# Start everything (Convex + Web + Native)
pnpm run dev
# Or individually:
pnpm run dev --filter backend # Convex backend
pnpm run dev --filter web # Next.js web app
pnpm run dev --filter native # Expo native appThe simplest way to seed data is through the Convex Dashboard. Navigate to your deployment at dashboard.convex.dev, select a table, and click "Add Document" to manually insert records.
Create a seed function in your Convex backend. Add the following to packages/backend/convex/seed.ts:
import { internalMutation } from "./_generated/server";
import { v } from "convex/values";
export const seedExample = internalMutation({
args: {},
returns: v.null(),
handler: async (ctx) => {
// Insert initial documents into your tables here.
return null;
},
});Run it from the Convex dashboard or CLI:
# Run via CLI
npx convex run seed:seedExample
# Or from the dashboard:
# Go to Functions > seed > seedExample > RunFor quick data inspection, use the Convex dashboard's "Run a query" feature. This lets you execute arbitrary read-only queries against your database without deploying code.
Important
Seed functions should be registered as internalMutation (not public) so they are not exposed to the public API. Only call them from the CLI or dashboard.
# Deploy to production (creates a production deployment)
npx convex deploy
# This will:
# 1. Push your schema and functions to production
# 2. Run any pending migrations
# 3. Give you production CONVEX_URL and CONVEX_SITE_URLAfter deploying, set all your environment variables on the production deployment:
# Set production env vars (use --prod flag or deploy first)
npx convex env set BETTER_AUTH_SECRET your_production_secret --prod
npx convex env set SITE_URL https://your-domain.com --prod
npx convex env set GOOGLE_CLIENT_ID your_prod_client_id --prod
npx convex env set GOOGLE_CLIENT_SECRET your_prod_secret --prod
# ... set all other env vars for production# Install Vercel CLI
npm i -g vercel
# Deploy from the web app directory
cd apps/web
vercel
# Set environment variables in Vercel dashboard:
# NEXT_PUBLIC_CONVEX_URL = your production convex URL
# NEXT_PUBLIC_CONVEX_SITE_URL = your production convex site URLConfigure your Vercel project with the root directory set to apps/web and the build command to cd ../.. && pnpm run build --filter web.
# Install EAS CLI
npm i -g eas-cli
# Configure EAS project
cd apps/native
eas build:configure
# Build for iOS
eas build --platform ios --profile production
# Build for Android
eas build --platform android --profile production
# Submit to App Store / Play Store
eas submit --platform ios
eas submit --platform androidAfter deploying, update your webhook URLs in the respective dashboards:
https://your-convex.site/api/webhook/polarhttps://your-convex.site/revenuecat/webhooksProduction Checklist
Authentication is powered by Better Auth with the Convex adapter. It supports email/password and Google OAuth out of the box. The auth configuration is in packages/backend/convex/lib/betterAuth/createAuth.ts.
Edit createAuth.ts and add the provider to the socialProviders section. Then set the required environment variables on your Convex deployment and update the auth client in both web and native apps.
User lifecycle events (create, update, delete) trigger callbacks defined in the backend. When a new user signs up, a profile is automatically created with default credits.
Polar handles web subscriptions and one-time purchases. Products are configured in the Polar dashboard and referenced by slug. Webhooks sync subscription status to the Convex database.
# Set Polar env vars on Convex
npx convex env set POLAR_ORGANIZATION_TOKEN your_token
npx convex env set POLAR_WEBHOOK_SECRET your_secretRevenueCat handles in-app purchases and subscriptions on iOS and Android. Configure your products in the RevenueCat dashboard and the App Store / Google Play Console. Webhooks sync purchase events to the Convex database.
Both payment platforms write to the same subscriptions table with a platform field ("polar" or "revenuecat"). Premium status is determined by checking for any active subscription regardless of platform.
Translations are shared between web and native via the @wellness/i18n package. Currently supports English, Spanish, and French.
The web app uses next-intl without i18n routing. Locale is detected from the browser or stored in a cookie. Use the useTranslations() hook in any client component.
import { useTranslations } from "next-intl";
function MyComponent() {
const t = useTranslations("common");
return <button>{t("save")}</button>;
}The native app uses expo-localization for device locale detection and i18n-js for translations. Use the useI18n() hook.
import { useI18n } from "@/contexts/i18n-context";
function MyScreen() {
const { t } = useI18n();
return <Text>{t("common.save")}</Text>;
}# 1. Create packages/i18n/messages/de.json (copy from en.json)
# 2. Add "de" to locales array in packages/i18n/index.ts
# 3. Add the import in packages/i18n/index.ts
# 4. Add the import in apps/native/lib/i18n.ts
# 5. Add export in packages/i18n/package.json exportsRTL locales (Arabic, Hebrew, Farsi, Urdu) are handled automatically. The web app sets dir="rtl" on the HTML element. The native app uses I18nManager.forceRTL().
File uploads use the @convex-dev/r2 Convex component. Files are stored in Cloudflare R2 and metadata is tracked in the Convex database.
# Set R2 credentials on Convex
npx convex env set R2_TOKEN your_cf_token
npx convex env set R2_ACCESS_KEY_ID your_key_id
npx convex env set R2_SECRET_ACCESS_KEY your_secret
npx convex env set R2_ENDPOINT https://xxx.r2.cloudflarestorage.com
npx convex env set R2_BUCKET your_bucket_nameThe English Tutor feature uses @convex-dev/agent for AI-powered conversations with streaming. It connects to AI providers via Vercel AI Gateway.
# Set AI Gateway key
npx convex env set AI_GATEWAY_API_KEY your_keycd apps/native
# Start Expo dev server
pnpm run dev
# Run on iOS simulator
pnpm run ios
# Run on Android emulator
pnpm run android
# Prebuild native code (after adding native modules)
pnpm run prebuildUpdate app.json with your app name, bundle identifiers, and icons. The current scheme is wellness:// -- change this to match your app's URL scheme.