Documentation

Everything you need to set up, customize, and deploy your SaaS application.

Architecture Overview
Monorepo structure and how everything connects

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 config

Key Technologies

  • Web: Next.js 16, React 19, shadcn/ui, Tailwind CSS 4, TanStack Query
  • Mobile: Expo 54, React Native, HeroUI Native, NativeWind
  • Backend: Convex (real-time DB), Better Auth, Polar, RevenueCat
  • AI: Convex Agent with Vercel AI Gateway
  • Storage: Cloudflare R2 via Convex plugin
Environment Setup
Configure environment variables for all services

1. Clone and Install

git clone <your-repo-url> wellness
cd wellness
pnpm install

2. Convex Backend Environment Variables

Set 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

3. Web App (.env.local)

# apps/web/.env.local
NEXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud
NEXT_PUBLIC_CONVEX_SITE_URL=https://your-deployment.convex.site

4. Native App (.env.local)

# 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

5. Start Development

# 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 app
Seeding Data
Populate your database with initial or test data

Option 1: Convex Dashboard (Manual)

The 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.

Option 2: Seed Script (Recommended)

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 > Run

Option 3: One-off Query (Quick Testing)

For 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
Step-by-step guide to deploying your entire stack

Step 1: Deploy Convex Backend to Production

# 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_URL

After 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

Step 2: Deploy Web App (Vercel)

# 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 URL

Configure your Vercel project with the root directory set to apps/web and the build command to cd ../.. && pnpm run build --filter web.

Step 3: Deploy Native App (EAS)

# 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 android

Step 4: Configure Webhooks

After deploying, update your webhook URLs in the respective dashboards:

  • Polar: Set webhook URL to https://your-convex.site/api/webhook/polar
  • RevenueCat: Set webhook URL to https://your-convex.site/revenuecat/webhooks
  • Google OAuth: Add your production domain to authorized redirect URIs

Production Checklist

  • Generate a new BETTER_AUTH_SECRET for production
  • Use production API keys for Google, Polar, RevenueCat
  • Set POLAR_SERVER to "production" (not "sandbox")
  • Update SITE_URL to your production domain
  • Verify webhook endpoints are accessible and responding
Authentication
Better Auth with Convex adapter

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.

Supported Methods

  • Email/Password (no email verification required by default)
  • Google OAuth
  • Apple Sign-In (native app)
  • Anonymous auth (native app)

Adding a New OAuth Provider

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.

Auth Triggers

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.

Payments
Polar for web, RevenueCat for mobile

Web Payments (Polar)

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_secret

Mobile Payments (RevenueCat)

RevenueCat 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.

Unified Subscriptions

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.

Internationalization (i18n)
Multi-language support with shared translations

Translations are shared between web and native via the @wellness/i18n package. Currently supports English, Spanish, and French.

Web (next-intl)

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>;
}

Native (expo-localization + i18n-js)

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>;
}

Adding a New Language

# 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 exports

RTL Support

RTL 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 (R2)
Cloudflare R2 storage via Convex plugin

File uploads use the @convex-dev/r2 Convex component. Files are stored in Cloudflare R2 and metadata is tracked in the Convex database.

Setup

# 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_name

Upload Flow

  1. Client requests a signed upload URL from Convex
  2. Client uploads file directly to R2 using the signed URL
  3. Client calls syncMetadata to record the upload in Convex
  4. Files are accessible via signed read URLs
AI English Tutor
Convex Agent with streaming responses

The English Tutor feature uses @convex-dev/agent for AI-powered conversations with streaming. It connects to AI providers via Vercel AI Gateway.

Setup

# Set AI Gateway key
npx convex env set AI_GATEWAY_API_KEY your_key

How It Works

  • Each user gets conversation threads stored in Convex
  • Messages stream in real-time using Convex subscriptions
  • The agent has a system prompt for English tutoring
  • Thread history is maintained for context-aware responses
Native Mobile App
Expo with React Native and shared backend

Running Locally

cd 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 prebuild

Key Features

  • Expo Router for file-based navigation
  • HeroUI Native components with multiple themes
  • RevenueCat for in-app purchases
  • Push notifications via Expo
  • Shared Convex backend with real-time sync
  • Better Auth with Expo plugin (Google + Apple OAuth)

Customizing the App

Update app.json with your app name, bundle identifiers, and icons. The current scheme is wellness:// -- change this to match your app's URL scheme.

Documentation | Fitsana