March 12, 2026·9 min·By

React Native with Expo: Build a Production Mobile App in 2026

React NativeExpomobileiOSAndroidEAS

Expo has matured to the point where you can build serious production apps without ever touching Xcode or Android Studio. Here's the modern workflow.

Setup

npx create-expo-app@latest MyApp --template tabs
cd MyApp
npx expo start

Scan the QR code with Expo Go on your phone to see it instantly. No build step for development.

File-Based Routing with Expo Router

app/
  _layout.tsx         # Root layout (fonts, auth gate, providers)
  (tabs)/
    _layout.tsx       # Tab bar layout
    index.tsx         # Home tab
    profile.tsx       # Profile tab
  auth/
    login.tsx         # /auth/login
    signup.tsx        # /auth/signup
  modal.tsx           # Modal screen
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router'
import { Ionicons } from '@expo/vector-icons'

export default function TabLayout() {
    return (
        <Tabs screenOptions={{ tabBarActiveTintColor: '#FCE38A' }}>
            <Tabs.Screen
                name="index"
                options={{
                    title: 'Home',
                    tabBarIcon: ({ color, size }) =>
                        <Ionicons name="home" size={size} color={color} />
                }}
            />
            <Tabs.Screen
                name="profile"
                options={{
                    title: 'Profile',
                    tabBarIcon: ({ color, size }) =>
                        <Ionicons name="person" size={size} color={color} />
                }}
            />
        </Tabs>
    )
}

Navigation

import { router, Link } from 'expo-router'

// Navigate programmatically
router.push('/profile')
router.push({ pathname: '/user/[id]', params: { id: '123' } })
router.back()

// Navigate via Link component
<Link href="/profile">Go to Profile</Link>
<Link href={{ pathname: '/user/[id]', params: { id: user.id } }}>
    View Profile
</Link>

Native APIs

import * as Camera from 'expo-camera'
import * as Location from 'expo-location'
import * as ImagePicker from 'expo-image-picker'

// Camera permission and capture
async function takePhoto() {
    const { status } = await Camera.requestCameraPermissionsAsync()
    if (status !== 'granted') return

    const result = await ImagePicker.launchCameraAsync({
        mediaTypes: ImagePicker.MediaTypeOptions.Images,
        quality: 0.8,
        allowsEditing: true,
        aspect: [4, 3]
    })

    if (!result.canceled) {
        uploadPhoto(result.assets[0].uri)
    }
}

// Location
async function getCurrentLocation() {
    const { status } = await Location.requestForegroundPermissionsAsync()
    if (status !== 'granted') return null

    const location = await Location.getCurrentPositionAsync({
        accuracy: Location.Accuracy.Balanced
    })
    return { lat: location.coords.latitude, lng: location.coords.longitude }
}

Push Notifications

import * as Notifications from 'expo-notifications'
import * as Device from 'expo-device'

Notifications.setNotificationHandler({
    handleNotification: async () => ({
        shouldShowAlert: true,
        shouldPlaySound: true,
        shouldSetBadge: true
    })
})

async function registerForPushNotifications(): Promise<string | null> {
    if (!Device.isDevice) return null  // Doesn't work in simulator

    const { status } = await Notifications.getPermissionsAsync()
    if (status !== 'granted') {
        const { status: newStatus } = await Notifications.requestPermissionsAsync()
        if (newStatus !== 'granted') return null
    }

    const token = await Notifications.getExpoPushTokenAsync({
        projectId: process.env.EXPO_PUBLIC_PROJECT_ID
    })
    return token.data
}

// Send from your backend via Expo Push API
async function sendPushNotification(expoPushToken: string, title: string, body: string) {
    await fetch('https://exp.host/--/api/v2/push/send', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            to: expoPushToken,
            title,
            body,
            data: { screen: 'notifications' }
        })
    })
}

EAS Build for App Store Submission

# Install EAS CLI
npm install -g eas-cli

# Login and configure
eas login
eas build:configure
// eas.json
{
    "cli": { "version": ">= 5.0.0" },
    "build": {
        "development": {
            "developmentClient": true,
            "distribution": "internal"
        },
        "preview": {
            "distribution": "internal",
            "ios": { "simulator": true }
        },
        "production": {
            "autoIncrement": true
        }
    },
    "submit": {
        "production": {}
    }
}
# Build for production
eas build --platform ios --profile production
eas build --platform android --profile production

# Submit to App Store / Play Store
eas submit --platform ios
eas submit --platform android

OTA Updates

The biggest advantage of Expo: deploy JavaScript updates without going through the App Store:

# Deploy an update instantly to all users
eas update --branch production --message "Fix checkout bug"

Native code changes still require a full build and store review. JavaScript/assets can update instantly.

The Expo ecosystem handles 90% of what mobile apps need. The 10% that requires native modules (custom camera filters, Bluetooth LE, specific hardware) usually has a community package. Use the bare workflow only if you're hitting that 10%.

K
Founder & Technical Lead, Innovibe

Building software for 15+ years. Passionate about AI, system design, and shipping things that work.

Frequently asked questions

Does Innovibe build this kind of thing for clients?+

Yes — this is exactly what we do day-to-day for clients across BC and Canada. If you'd rather have us build and maintain it than implement it yourself, reach out.

How do I decide whether to build this in-house or hire an agency?+

Build in-house if your team has the skills and bandwidth and this is core to your product. Hire out if it's infrastructure, if speed matters, or if the expertise gap would take months to close. We're biased, obviously — but we'll tell you honestly when in-house makes more sense.

What tech stack does Innovibe use for projects like this?+

Next.js + TypeScript on the frontend, Node.js or Go on the backend, Postgres for the primary data store, and GCP (Cloud Run, BigQuery, Pub/Sub) for infrastructure. We pick tools that are boring in the best way — proven, well-documented, and easy to hire for.

Building something with AI?

We scope and ship AI features quickly. Let's talk.

Start a Conversation