React Native – Set Up a Project with Expo

June 13, 20263 min readUpdated 8/24/2026

One command creates the project. The interesting part is what it generates, and one decision it does not make for you.

Creating a project

npx create-expo-app@latest my-app --template blank-typescript
cd my-app
npm install

That is the whole scaffold. There is no global CLI to install and nothing to keep up to date — npx fetches the current version each time.

Expo Go versus a development build

This is the decision, and getting it wrong wastes an afternoon.

Expo Go is an app you install from the store. npx expo start serves your JavaScript to it and you scan a QR code. Nothing is compiled, so it starts in seconds — and it contains a fixed set of native modules chosen by Expo. Anything else cannot load.

A development build is your own app binary, containing exactly the native code your project declares:

npx expo run:ios
npx expo run:android

It takes minutes the first time and seconds afterwards, and from then on it behaves like Expo Go — same fast refresh, same dev menu — except it can load your native dependencies.

The rule: the moment you add a library with native code, you need a development build. The demo app links Stripe's native SDK, so it does. If a tutorial tells you Expo cannot use native modules, it is describing Expo Go and it is years out of date.

What got generated

Less than you might expect, and deliberately so:

app/            your screens — the folder structure IS the navigation graph
assets/         icons and the splash image
app.json        app configuration
package.json
tsconfig.json

Note what is absent: no ios/ and no android/. Those are generated on demand from your configuration by npx expo prebuild, which is what run:ios calls. Treating them as build output rather than source is the single biggest practical difference from a bare React Native project — upgrades stop being merge conflicts in files nobody understands.

app.json, or app.config.ts

The scaffold gives you app.json. Rename it to app.config.ts the moment you need a value from the environment, because JSON cannot read one:

const config: ExpoConfig = {
  name: 'StayHub Pizza',
  slug: 'pizza-react-native-mobile',
  version: '1.0.0',
  orientation: 'portrait',
  icon: './assets/icon.png',
  scheme: 'pizzaapp',
  userInterfaceStyle: 'light',

Typing it as ExpoConfig is worth doing on its own — Expo SDK 57 removed several keys that older tutorials still use, and the compiler tells you immediately rather than the build failing later.

scheme matters more than it looks: it is what lets a link open your app, and what Stripe uses to return the customer after a 3D Secure redirect.

Environment values

Two mechanisms, and they are not interchangeable. Anything prefixed EXPO_PUBLIC_ is inlined into the bundle and readable by anyone who downloads the app — so it is for public values only, like a Stripe publishable key. Everything else goes through extra in the config and is resolved at build time.

Neither is a secret store. A mobile bundle is downloadable and inspectable; anything that must stay secret lives on your server.

The scripts you will actually run

    "start": "expo start",
    "ios": "expo run:ios",
    "android": "expo run:android",
    "web": "expo start --web",
    "prebuild": "expo prebuild",

expo start --web renders the same components in a browser through react-native-web. It is a genuinely useful preview and it is not the product — several things do not work there, and later lessons name them as they come up.

Path aliases

Relative imports rot fast once you have a folder or two. TypeScript's paths fixes it, and Metro reads the same file:

    "paths": {
      "@/*": ["./src/*"]
    },

So a screen imports @/features/cart/state/CartProvider rather than counting ../ segments. There is no second copy of this mapping to keep in sync — Expo's Metro config picks it up from tsconfig.json directly.

What you need installed

Node 22. For iOS, Xcode 16.1 or newer — React Native 0.86 will not build on 15.x — plus an actual simulator runtime, which a fresh Xcode install often lacks. Check both:

xcodebuild -version
xcrun simctl list runtimes

If the second prints nothing, download a runtime in Xcode before you spend an hour reading a build error that does not mention it. For Android, Android Studio and one AVD.

Sanity-checking the project

npx expo-doctor

It catches version mismatches between Expo and its libraries, and missing native peer dependencies. That last one is worth the habit: a missing peer typically builds fine and then crashes on launch, which is a bad place to discover it.

What is next

Core Components — the parts you build screens out of, and why there is no <div>.