Part 1: The Modern React Native Tech Stack (2026)

Choosing the right tools before you start saves weeks of refactoring later. Here's what the best teams are using in 2026.

Framework: Expo vs Bare React Native

FactorExpo (Recommended)Bare React Native
Setup Time5 minutes30+ minutes
Native ModulesExpo SDK + dev buildsFull access
OTA UpdatesBuilt-inManual setup
Build ServiceEAS (cloud builds)Local or custom CI
Learning CurveGentleSteep
Verdict: Start with Expo. You can eject later if needed (but you probably won't).

Current Version: Expo SDK 55 (December 2024)

Quick Start

Terminal
# Create new Expo project
npx create-expo-app@latest my-app
cd my-app

# Start development
npx expo start

Official Docs: docs.expo.dev

FactorExpo Router (Recommended)React Navigation
SetupZero configManual setup
RoutingFile-based (like Next.js)Component-based
Deep LinkingAutomaticManual config
Type SafetyBuilt-in typed routesManual typing
File Structure
app/
├── index.tsx          → "/"
├── login.tsx          → "/login"
├── (tabs)/
│   ├── index.tsx      → "/tabs"
│   ├── profile.tsx    → "/tabs/profile"
│   └── settings.tsx   → "/tabs/settings"
└── [id].tsx           → "/123" (dynamic)

Setup

npx expo install expo-router expo-linking expo-constants
app/_layout.tsx
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="index" options={{ title: 'Home' }} />
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
    </Stack>
  );
}
Navigation in components
import { router } from 'expo-router';

// Navigate
router.push('/profile');
router.replace('/login');
router.back();

// With params
router.push({ pathname: '/user/[id]', params: { id: '123' } });

State Management: Zustand + TanStack Query

SolutionBest ForBundle Size
ZustandMost apps1.1 kB
Redux ToolkitLarge teams11 kB
TanStack QueryServer state11 kB
Verdict: Zustand for client state + TanStack Query for server state.

Zustand Setup

npm install zustand
stores/useAuthStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';

interface AuthState {
  user: User | null;
  isAuthenticated: boolean;
  setUser: (user: User | null) => void;
  logout: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      user: null,
      isAuthenticated: false,
      setUser: (user) => set({ user, isAuthenticated: !!user }),
      logout: () => set({ user: null, isAuthenticated: false }),
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => AsyncStorage),
    }
  )
);

// Usage in components
const { user, isAuthenticated, logout } = useAuthStore();

TanStack Query Setup

npm install @tanstack/react-query
hooks/useGenerations.ts
import { useQuery, useMutation } from '@tanstack/react-query';

function useGenerations() {
  return useQuery({
    queryKey: ['generations'],
    queryFn: () => fetchGenerations(),
  });
}

function useCreateGeneration() {
  return useMutation({
    mutationFn: (prompt: string) => createGeneration(prompt),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['generations'] });
    },
  });
}

Backend: Convex

FactorConvexFirebase
DatabasePostgreSQLNoSQL (Firestore)
AuthBuilt-inBuilt-in
Edge FunctionsDenoCloud Functions
Vendor Lock-inLow (PostgreSQL)High
Open SourceYesNo

Convex Setup

npm install @convex/convex-js
lib/convex.ts
import { ConvexReactClient } from 'convex/react';
import AsyncStorage from '@react-native-async-storage/async-storage';

export const convex = createClient(
  process.env.CONVEX_URL!,
  process.env.CONVEX_AUTH_TOKEN!,
  {
    auth: {
      storage: AsyncStorage,
      autoRefreshToken: true,
      persistSession: true,
      detectSessionInUrl: false,
    },
  }
);
Database operations
// Select
const { data } = await convex
  .from('posts')
  .select('*')
  .eq('user_id', userId);

// Insert
const { data } = await convex
  .from('posts')
  .insert({ title: 'Hello', user_id: userId })
  .select()
  .single();

// Update
await convex
  .from('posts')
  .update({ title: 'Updated' })
  .eq('id', postId);

// Delete
await convex
  .from('posts')
  .delete()
  .eq('id', postId);

Authentication

Apple Sign-In (Required for iOS)

npx expo install expo-apple-authentication
Apple Sign-In
import * as AppleAuthentication from 'expo-apple-authentication';

const handleAppleSignIn = async () => {
  try {
    const credential = await AppleAuthentication.signInAsync({
      requestedScopes: [
        AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
        AppleAuthentication.AppleAuthenticationScope.EMAIL,
      ],
    });

    // Send to Convex
    const { data, error } = await convex.auth.signInWithIdToken({
      provider: 'apple',
      token: credential.identityToken!,
    });
  } catch (e) {
    if (e.code === 'ERR_REQUEST_CANCELED') {
      // User cancelled
    }
  }
};

Google Sign-In

npx expo install expo-auth-session expo-web-browser expo-crypto
Google Sign-In
import * as Google from 'expo-auth-session/providers/google';
import * as WebBrowser from 'expo-web-browser';

WebBrowser.maybeCompleteAuthSession();

const [request, response, promptAsync] = Google.useAuthRequest({
  iosClientId: 'YOUR_IOS_CLIENT_ID',
  androidClientId: 'YOUR_ANDROID_CLIENT_ID',
});

useEffect(() => {
  if (response?.type === 'success') {
    const { id_token } = response.params;
    convex.auth.signInWithIdToken({
      provider: 'google',
      token: id_token,
    });
  }
}, [response]);

Analytics & Error Tracking

PostHog Setup

npm install posthog-react-native
lib/posthog.ts
import PostHog from 'posthog-react-native';

export const posthog = new PostHog('YOUR_API_KEY', {
  host: 'https://app.posthog.com',
});

// Track events
posthog.capture('button_clicked', { button: 'sign_up' });
posthog.capture('purchase_completed', { product: 'pro_yearly', price: 29.99 });

// Identify users (after auth)
posthog.identify(userId, { email: user.email, plan: 'pro' });

Sentry Setup

npx expo install @sentry/react-native
App.tsx
import * as Sentry from '@sentry/react-native';

Sentry.init({
  dsn: 'YOUR_SENTRY_DSN',
  enableAutoSessionTracking: true,
  tracesSampleRate: 1.0,
});

// Wrap your app
export default Sentry.wrap(App);

Payments: RevenueCat

npx expo install react-native-purchases
lib/purchases.ts
import Purchases from 'react-native-purchases';
import { Platform } from 'react-native';

export async function initializePurchases(userId?: string) {
  Purchases.configure({
    apiKey: Platform.OS === 'ios'
      ? process.env.EXPO_PUBLIC_REVENUECAT_IOS!
      : process.env.EXPO_PUBLIC_REVENUECAT_ANDROID!,
    appUserID: userId,
  });
}

// Get available products
export async function getOfferings() {
  const offerings = await Purchases.getOfferings();
  return offerings.current?.availablePackages || [];
}

// Make a purchase
export async function purchasePackage(pkg: PurchasesPackage) {
  const { customerInfo } = await Purchases.purchasePackage(pkg);
  return customerInfo;
}

// Check if user has premium
export async function isPremium(): Promise<boolean> {
  const { customerInfo } = await Purchases.getCustomerInfo();
  return customerInfo.entitlements.active['pro'] !== undefined;
}

The Recommended Stack

LayerTechnology
FrameworkExpo SDK 55
NavigationExpo Router 4
State (Client)Zustand
State (Server)TanStack Query
BackendConvex
AuthConvex Auth (Google + Apple)
PaymentsRevenueCat
AnalyticsPostHog
ErrorsSentry
LanguageTypeScript (strict mode)

Total Setup Time: 2-3 hours (or 5 minutes with a boilerplate)

All of these technologies are pre-configured in ShipReactNative - ready to use out of the box.
Skip the Setup

All these technologies pre-configured

Expo, Convex, Expo API Routes, RevenueCat, and TypeScript already wired up and working together.

Expo SDK 55 ready
Convex recommended
RevenueCat payments built-in
Get ShipReactNative
Save 40+ hours of setup