Architecture

Understand the runtime architecture, providers, gates, and navigation.

The Expo template keeps architecture visible in a few files instead of hiding it behind a framework layer. app/_layout.tsx is the runtime composition root, Expo Router owns navigation, TanStack Query owns server state, and provider hooks are the dependency-injection surface.

Routing

Expo Router maps files to routes and app.config.ts enables experiments.typedRoutes.

PatternFile or exampleNotes
Route groupsapp/(auth), app/(tabs)Group files without changing the URL path
Tabsapp/(tabs)/_layout.tsxFour visible tabs plus hidden tab-context screens with href: null
Pushed screensapp/todos.tsx, app/profile.tsx, app/permissions.tsxOpened from drawer rows, cards, or deep links
Dynamic routesapp/component/[slug].tsxReads slug with useLocalSearchParams()
ModalsStack.Screen name="paywall" and name="redeem-code"Root stack sets presentation: 'modal'
Fallbackapp/+not-found.tsxHandles unknown paths
Web shellapp/+html.tsxUsed by pnpm web and Metro static output

Deep-linkable paths are intentionally allow-listed in lib/deep-links.ts. Adding a route does not automatically make it safe for push notifications, referral links, or external URLs.

Provider stack

RootLayout is wrapped by Sentry.wrap(...) and renders this order:

OrderProvider/componentWhy it is there
1RootErrorBoundaryBranded runtime fallback and Sentry capture
2GestureHandlerRootView, KeyboardProvider, SafeAreaProviderRequired native infrastructure for gestures, keyboard, and insets
3QueryProviderTanStack Query cache, persistence, focus/online managers
4AuthProviderSupabase session and auth actions
5AnalyticsProviderPostHog capture, identify/reset, consent-aware queueing
6ConsentProvider, OnboardingProviderPersisted legal and onboarding preferences
7EntitlementProvider, PurchasesProviderSupabase entitlement read-model plus RevenueCat purchase mechanism
8I18nProvidert, locale, and setLocale
9ThemePreferenceProvider, React Navigation ThemeProviderNativeWind preference plus navigation colors
10NotificationsProvider, BiometricProvider, ToastProvider, BottomSheetModalProviderApp services and global UI surfaces

The gates are not wrapper components around the navigator. They are siblings inside the provider tree: OnboardingGate, AuthGate, and VersionGate render before the root Stack; ConsentGate and BiometricGate render after it as overlay-style gates.

Use hooks as the injection surface: useAuth(), useI18n(), useEntitlement(), usePurchases(), useToast(), useNotifications(), useThemePreference(), and service-specific helpers.

State conventions

State kindOwner
Server stateTanStack Query in lib/query.tsx and screen hooks
Supabase sessionAuthProvider, backed by SecureStore chunking on native
EntitlementEntitlementProvider, reading the subscriptions table
PurchasesPurchasesProvider, RevenueCat SDK state and purchase actions
App preferencesSmall providers backed by AsyncStorage/SecureStore/localStorage
UI notificationsToastProvider, NotificationsProvider, bottom sheets/dialogs

There is no Redux or Zustand store. Add one only if a future feature has global client state that is not a server cache, auth/session state, or a simple persisted preference.

Styling

Styling is split across three connected layers:

LayerFileRole
NativeWind classesScreens and componentsclassName for layout, type, spacing, and tokens
CSS variablesglobal.cssLight/dark shadcn-style and iOS grouped color tokens
JS tokenslib/theme.tsTHEME, IOS_COLORS, getIOSColor(), and NAV_THEME

Reusable variants use CVA, for example buttonVariants, buttonTextVariants, and textVariants. Platform fallbacks use the normal React Native file pattern: store-review.web.ts and tracking-permissions.web.ts no-op where native APIs do not exist.

Code style and checks

The template uses strict TypeScript, Prettier with Tailwind class sorting, Expo's ESLint flat config, Husky, and lint-staged.

pnpm typecheck
pnpm lint
pnpm format:check

There is no Jest/Vitest setup in the template today. The shipped automated checks are TypeScript, Expo lint, formatting, and the Deno test for the RevenueCat webhook mapper:

deno test supabase/functions/_shared/revenuecat.test.ts

On this page