Architecture Overview

High-level overview of the Ship React Native boilerplate architecture. The current repo supports two backend modes: Convex as the recommended full backend and Expo API Routes as the lighter server-side alternative.

Project Structure

ship-react-native/
ship-react-native/
├── src/
│   ├── app/                  # Expo Router pages (file-based routing)
│   │   ├── _layout.tsx       # Root layout with providers
│   │   ├── index.tsx         # Entry point with auth/paywall routing
│   │   ├── api/              # Expo API Routes (lighter backend mode)
│   │   ├── (app)/            # App routes
│   │   ├── onboarding.tsx    # Onboarding flow
│   │   └── login.tsx         # Authentication
│   ├── components/           # Reusable components
│   ├── constants/            # Brand, features, backend mode, config
│   ├── hooks/                # App hooks and backend-specific hooks
│   ├── providers/            # Convex, Query, and root providers
│   ├── services/             # AI, purchases, notifications, auth
│   ├── store/                # Zustand state stores
│   ├── locales/              # i18n translations
│   └── utils/                # Helpers and storage
│
├── convex/                   # Recommended full backend
├── scripts/                  # Setup and utility scripts
└── __tests__/                # Jest tests
/                          # Entry point (index.tsx)
├── /onboarding           # Cinematic onboarding flow
├── /login                # Authentication
├── /privacy              # Privacy policy
├── /terms                # Terms of service
│
└── /(app)                # Authenticated routes
    ├── /(tabs)           # Tab navigation
    │   ├── /             # Home (index)
    │   ├── /gallery      # Gallery
    │   └── /settings     # Settings
    │
    ├── /generate         # Image generation
    ├── /video-generation # Video generation
    ├── /speech           # Text-to-speech
    ├── /transcription    # Speech-to-text
    ├── /image-identify   # Image analysis
    ├── /chat             # AI chat
    └── /premium          # Paywall

State Management

Zustand stores handle all app state with AsyncStorage persistence:

StoreStateActions
useAuthStoreuser, session, isLoadingsignIn(), signOut()
useAIStorecredits, isGenerating, historygenerate(), addCredits()
usePremiumStoreisSubscribed, offeringspurchase(), restore()
useGeneralStoreApp-wide preferences
useSettingsStoreUser settings

Backend Architecture

Ship React Native supports two backend paths. Convex is the recommended full backend for auth, persistence, credits, subscriptions, and AI orchestration. Expo API Routes are the lighter alternative when you mainly need secure server-side AI endpoints.

Convex Mode
Mobile App (React Native)
  ↓  useQuery / useMutation / useAction
Convex
  ↓  auth, database, credits, subscriptions, AI actions
Provider APIs
  ↓  JSON / media result
Mobile App (display result)
Expo API Routes Mode
Mobile App (React Native)
  ↓  fetch("/api/[feature]", { body: userInput })
Expo API Route  ← server-side env vars
  ↓  fetch("https://api.openai.com/...")
Provider APIs
  ↓  JSON result
Mobile App (display result)

Backend Selection

Backend mode is selected in src/constants/api.ts. The current repo defaults to convex.

export type ApiMode = "convex" | "expo-api-routes";
export const API_MODE: ApiMode = "convex";

Hard Paywall Architecture

The boilerplate uses a 3-layer hard paywall system. Every layer serves a distinct purpose — together they guarantee no free access to app features.

Layer 1: index.tsx (Entry Router)

src/app/index.tsx
// App launches -> index.tsx checks:
//   1. Onboarding completed? No -> /onboarding
//   2. isPremium? No -> /(app)/premium
//   3. isPremium? Yes -> /(app)/(tabs)

useEffect(() => {
  if (isLoading || isPremiumLoading) return;

  if (!isOnboardingCompleted) {
    router.replace("/onboarding");
  } else if (!isPremium) {
    router.replace("/(app)/premium");
  } else {
    router.replace("/(app)/(tabs)");
  }
}, [isOnboardingCompleted, isPremium, isPremiumLoading, isLoading]);

Why: Handles the initial routing decision on cold start. Waits for usePremiumStatus to finish loading before routing, preventing flash of wrong screen.

Layer 2: (app)/_layout.tsx (Guard)

src/app/(app)/_layout.tsx
useEffect(() => {
  if (isStatusLoading) return;
  const onPremium = segments.some((s) => s === "premium");
  if (!isSubscribed && !onPremium) {
    router.replace("/(app)/premium");
  }
}, [isSubscribed, isStatusLoading, segments, router]);

Why: Catches edge cases where a user navigates directly to an app route (deep link, back button) without being subscribed. Acts as a safety net.

Layer 3: usePremiumStatus Hook

src/hooks/usePremiumStatus.ts
// Checks RevenueCat on mount, then every 5 minutes
// Syncs result to shared usePremiumStore (Zustand)
const isPremium = await purchasesService.checkSubscriptionStatus();
usePremiumStore.setState({ isSubscribed: isPremium });

Why: Single source of truth for subscription state. Both Layer 1 and Layer 2 read from usePremiumStore.

Purchase Flow

After successful purchase
// 1. User taps Subscribe on premium.tsx
// 2. purchasePackage() calls RevenueCat
// 3. On success, refreshPremiumStatus() re-checks entitlements
// 4. usePremiumStore.isSubscribed becomes true
// 5. Layer 2's useEffect fires — user can now access app
// 6. After confetti animation, navigate to /(app)/(tabs)
Dev skip button: In __DEV__ mode, the premium screen shows a "Skip (Dev)" button. It must call usePremiumStore.setState({ isSubscribed: true }) — not just markPremiumScreenSeen(). The latter only marks onboarding as done, it does not bypass the paywall guard.

Security Architecture

LayerWhat's Protected
Mobile AppNo AI API keys in bundle. Only public RevenueCat/PostHog keys.
Convex / Expo API RoutesAI keys stay server-side. Validate inputs. Enforce auth and limits in the selected backend layer.
RevenueCatPremium status verification — subscription-gated features.

Component Hierarchy

App (_layout.tsx)
├── Providers
│   ├── ThemeProvider        ← dark/light mode
│   ├── PurchasesProvider    ← RevenueCat
│   └── PostHogProvider      ← Analytics
│
└── Navigator (Expo Router)
    ├── OnboardingScreen     ← Cinematic branded slides
    ├── LoginScreen          ← Auth (anonymous or email)
    └── AppLayout
        ├── TabNavigator
        │   ├── HomeScreen
        │   └── SettingsScreen
        │
        └── StackScreens
            ├── FeatureScreen   ← Your AI feature
            └── PremiumScreen   ← VidNotes-style paywall

Performance Considerations

OptimizationImplementation
Lazy loadingScreens loaded on demand via Expo Router
MemoizationReact.memo for expensive components
Virtualized listsFlatList for galleries
Background tasksAI generation processed server-side
Image cachingexpo-image for efficient caching
Production Architecture

Everything wired up correctly

Convex or Expo API Routes, Zustand stores, RevenueCat, and the design system — all connected.

Convex mode
Expo API Routes mode
RevenueCat paywall
Get ShipReactNative
Save 40+ hours of setup