Mobile Development 8 min read 2,912 views

React Native and Expo in 2026: Building and Shipping Mobile Apps

A practical 2026 guide to React Native with Expo SDK 57 — choosing Expo or bare, expo-router, config plugins, EAS Build, over-the-air updates and store submission.

Mobile app development

The official Expo documentation is excellent at telling you what each API does. It is much quieter on the decisions: whether to use Expo at all, what happens the first time you need native code, and what actually stands between a working simulator build and an app in the store.

This guide covers those. Versions at the time of writing: Expo SDK 57, React Native 0.86 and expo-router 57.

Expo or bare React Native?

This used to be a real fork in the road. It is much less so now — the old trade-off, that Expo blocked you from native code, no longer holds. Config plugins let a managed project add native dependencies without ejecting.

Expo (managed)Bare React Native
Setup timeMinutesHours, plus Xcode and Android Studio
Native codeVia config pluginsDirect
BuildsEAS Build, cloud or localYour own machines or CI
Over-the-air updatesBuilt inRoll your own
UpgradesOne SDK bumpManual, per package
Best forAlmost everythingDeep native work, existing native app

Start with Expo. The genuine reasons to go bare are narrow: you are adding React Native to an existing native application, or you maintain native modules yourself. Everything else is better served by the managed workflow, and you can still drop to native when you need to.

Starting a project

npx create-expo-app@latest my-app
cd my-app
npx expo start

That gives you TypeScript and expo-router by default. The routing choice matters more than it looks, so it is worth understanding rather than accepting.

File-based routing with expo-router

Routes are files. The structure below produces a tab bar with two tabs and a dynamic product screen:

app/
  _layout.tsx          # root layout, providers go here
  (tabs)/
    _layout.tsx        # tab navigator
    index.tsx          # /
    profile.tsx        # /profile
  products/
    [id].tsx           # /products/123
  +not-found.tsx
// app/products/[id].tsx
import { useLocalSearchParams, Stack } from 'expo-router'
import { Text, View } from 'react-native'

export default function ProductScreen() {
  const { id } = useLocalSearchParams<{ id: string }>()

  return (
    <View>
      <Stack.Screen options={{ title: `Product ${id}` }} />
      <Text>Showing product {id}</Text>
    </View>
  )
}

The payoff beyond tidiness is that these routes are real URLs. Deep links and universal links work without a separate linking configuration, because the file path is the path.

Native code without ejecting

This is the part most guides skip, and the point where people wrongly conclude they must leave Expo.

You do not edit ios/ and android/ directly. You declare what those directories should contain, and Expo generates them at build time:

// app.json
{
  "expo": {
    "name": "My App",
    "slug": "my-app",
    "plugins": [
      "expo-camera",
      [
        "expo-build-properties",
        {
          "ios": { "deploymentTarget": "15.1" },
          "android": { "compileSdkVersion": 35 }
        }
      ]
    ],
    "ios": { "bundleIdentifier": "com.example.myapp" },
    "android": { "package": "com.example.myapp" }
  }
}

The key consequence: the native directories are build artefacts, not source. They are regenerated, so hand edits are lost. If you ever find yourself editing them, either the change belongs in a config plugin or you have genuinely outgrown the managed workflow.

EAS Build

Building iOS apps traditionally means owning a Mac and wrestling with certificates. EAS Build moves that to a hosted machine.

// eas.json
{
  "cli": { "version": ">= 5.0.0" },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal",
      "channel": "preview"
    },
    "production": {
      "channel": "production",
      "autoIncrement": true
    }
  },
  "submit": { "production": {} }
}
eas build --platform ios --profile production
eas build --platform android --profile production

Set up a development build early. Expo Go is convenient for a first look, but it only contains the native modules Expo ships. The moment you add a library with its own native code, Expo Go cannot run your app — and it fails in a way that reads like a broken install rather than an unsupported one. A development build includes your actual native dependencies and behaves like the real app.

Over-the-air updates

eas update --branch production --message "Fix checkout validation"

This ships changed JavaScript straight to installed apps, bypassing store review. It is genuinely valuable for fixing a bad copy string or a validation bug the same day.

The limit worth knowing: OTA updates carry JavaScript and assets only. Anything that changes native code — a new library with native dependencies, an SDK upgrade, a permission change — requires a new binary and a store submission. Confusing the two is the most common cause of "the update went out but nothing changed".

Submitting to the stores

eas submit --platform ios --latest
eas submit --platform android --latest

Two things reliably cost people their first week, and neither is a code problem:

Permission strings. Apple rejects any app that requests a permission without explaining why, in the user's language. Declare them explicitly rather than letting a library supply a default:

"ios": {
  "infoPlist": {
    "NSCameraUsageDescription": "We use the camera so you can photograph a receipt.",
    "NSPhotoLibraryUsageDescription": "We access your photos so you can attach one to a claim."
  }
}

Android target API level. Google Play enforces a minimum target level and raises it every year. An app below it cannot be updated — not a warning, a hard block. Check it before each release rather than discovering it at submission.

The New Architecture

React Native's New Architecture — Fabric and TurboModules — is the default in current versions. For most application code you will not notice: the same components, the same hooks.

Where it shows up is third-party libraries. An unmaintained package written against the old architecture may not work. Before adopting any library, check when it was last released and whether it declares New Architecture support. This is now the single most common upgrade blocker, and it is worth checking before you depend on something rather than after.

Performance: the three that actually matter

  • Use FlatList properly, or FlashList. Rendering a long list with .map() mounts every row at once. Lists are where mobile performance is won or lost.
  • Keep animations off the JavaScript thread. Reanimated runs them on the UI thread, so they stay smooth even while JavaScript is busy. Animations driven from JS stutter under load.
  • Watch bundle size and image dimensions. Shipping a 4000px image to display it at 200px wastes memory and decode time on exactly the low-end devices you cannot test on.

Mistakes worth avoiding

  • Staying on Expo Go too long. It cannot run custom native modules. Move to a development build as soon as you add one.
  • Editing ios/ and android/ by hand. They are regenerated. Put the change in a config plugin.
  • Expecting OTA to ship native changes. It cannot. New native code needs a new binary.
  • Testing only on a simulator. Performance, permissions, camera and push notifications all behave differently on a real device.
  • Skipping SDK upgrades. One SDK bump is routine. Four at once is a project.

A sensible path

Start with create-expo-app and expo-router. Move to a development build as soon as you need a native module. Wire up EAS Build early — well before launch — so that the build works long before it has to. Add OTA updates for JavaScript fixes, and keep upgrading one SDK at a time.

None of this is exotic. Most React Native projects that go badly do so not because the framework failed, but because the build and release path was left until the end.

Frequently asked questions

Should I use Expo or bare React Native in 2026?

Use Expo unless you are adding React Native to an existing native app or you maintain native modules yourself. Config plugins mean the managed workflow no longer blocks native code, so the old reason for going bare has largely gone.

Can I use native modules with Expo?

Yes. Add the library, declare it in app.json via a config plugin, and build a development build. You never edit ios/ or android/ yourself — Expo generates them from your configuration.

What is the difference between Expo Go and a development build?

Expo Go is a prebuilt app containing only Expo's own native modules, useful for a first look. A development build is your app, with your native dependencies compiled in. Once you add any custom native code, Expo Go cannot run your project.

Do I need a Mac to build an iOS app with Expo?

No. EAS Build compiles on hosted macOS machines, so you can build and submit an iOS app from Windows or Linux. A Mac is still convenient for the iOS simulator, but it is no longer required to ship.

What can over-the-air updates actually change?

JavaScript and assets only. Bug fixes, copy and UI changes ship instantly. Anything touching native code — a new native dependency, an SDK upgrade, a permission change — needs a new binary and store review.

Why does my app work in Expo Go but fail after building?

Usually a native module that Expo Go includes but your configuration does not declare, or a permission missing from app.json. Build a development build and read the native logs; the failure is almost always visible there rather than in the JavaScript console.

How often should I upgrade the Expo SDK?

Every release, or at worst every other one. Upgrading one SDK version is usually a short task with a codemod. Skipping several turns it into a multi-day migration, and unmaintained libraries make it worse the longer you wait.

Is the New Architecture safe to use?

Yes — it is the default in current React Native versions and application code rarely needs changing. The risk sits in third-party libraries: check that anything you depend on is actively maintained and declares support, ideally before you adopt it.

Share this article:
ES
Written by

Edrees Salih

Full-stack software engineer with 9 years of experience. Passionate about building scalable solutions and sharing knowledge with the developer community.

View Profile

Comments (0)

Leave a Comment

Your email will not be published.

No comments yet. Be the first to share your thoughts!

Related Articles

Related Articles

Need Help With Your Project?

Book a free 30-minute consultation to discuss your technical challenges and explore solutions together.