AniUI Academy

Project Structure and the Entry Point

How a typical React Native project is laid out, what actually happens at app launch, and where "the app starts here" lives in both Expo and bare projects.

7 min read

Open an unfamiliar web project and you know roughly where to look: index.html, a bundler config, and a component tree hanging off some root render call. A React Native project has an equivalent shape — it just has one extra layer, because there's a real native app underneath your JavaScript, not a browser.

The shape of a typical project

A React Native project's exact folder layout depends on whether it's Expo managed or bare CLI, but the recognizable pieces are consistent:

  • A JavaScript/TypeScript entry file — commonly App.js/App.tsx at the project root, or an app/ directory if the project uses a file-based routing convention (as Expo Router–style projects do). This is where your component tree begins.
  • package.json, exactly like any Node-based JS project — dependencies, scripts, and project metadata.
  • A bundler configuration, metro.config.js. Metro is React Native's JavaScript bundler — conceptually the same job a web bundler does (resolving imports, packaging source and assets into a bundle), just producing a bundle a native runtime loads instead of one a browser fetches.
  • ios/ and android/ native project folders — present and directly editable in a bare React Native CLI project (these are real Xcode and Android Studio/Gradle projects), and present but generally not hand-edited day-to-day in a typical Expo managed project, since Expo's tooling and EAS Build handle that layer for you.

None of this is exotic once you see it laid out — it's the same "source code plus bundler plus native shell" shape any cross-platform JS runtime needs, just with two real native projects instead of a browser as the final consumer of your bundle.

The entry point: where "the app starts" actually lives

On the web, the browser loads index.html, which loads your bundle, which typically calls something like createRoot(...).render(<App />). React Native needs the equivalent handshake, but the "browser" side of that handshake is native code instead.

Conceptually, the native app shell needs to be told which JavaScript component is the root of your app. That's what AppRegistry.registerComponent does at its core — it's the call that tells the native runtime "when you're ready to show a UI, this is the component to render":

import { AppRegistry } from "react-native";
import App from "./App";
 
AppRegistry.registerComponent("MyApp", () => App);

In an Expo managed project, this registration is handled for you as part of the managed entry point — you generally don't write this call yourself, but the underlying idea is identical: some registration step tells native code which component is the app's root.

The boot sequence, conceptually

Because there's a real native app underneath, launching a React Native app has one more step than a web page load. At a conceptual level — the exact mechanics differ between the old and newer native runtimes, which is a later lesson's topic — the sequence looks like this:

  1. Step 1

    Native app launches

    The OS starts the native app shell, same as launching any native iOS or Android app.

  2. Step 2

    JS bundle loads

    The native side loads and begins executing the JavaScript bundle produced by Metro.

  3. Step 3

    Root component registers and renders

    The registered root component (App) runs — the same React rendering you already know, hooks and all.

  4. Step 4

    UI appears

    The rendered component tree is turned into real native views, which the user actually sees and can interact with.

A conceptual sequence — this track's later lessons go deeper on what actually connects steps 3 and 4.

The important takeaway isn't the exact API names, which do shift over time and across native-runtime versions — it's the shape: native code has to be running before your JavaScript can execute, and your JavaScript has to run and register a root component before anything visible can appear. "The app starts here" is genuinely two starting points, native and JS, meeting at a registration call.

Why this matters day to day

You'll rarely touch the entry point once a project is set up — most feature work happens entirely inside your component tree, same as any React app. But understanding this sequence explains real, ordinary confusions: why a native-side crash can happen before your JS ever runs, why changing native project settings (like ios//android/ files in a bare project) requires a native rebuild rather than just reloading JS, and why "the JS bundle failed to load" is a distinct category of failure from "a component threw an error" — the former means your app never got past step 2 above.

What to remember

  • A React Native project has your JS/TS source (App.js/App.tsx or an app/ directory), package.json, a Metro bundler config, and native ios/android project folders.
  • ios/ and android/ are real native projects — directly editable in bare CLI, generally managed for you in a typical Expo project.
  • AppRegistry.registerComponent (or Expo's managed equivalent) is the handshake that tells native code which component is the app's root.
  • Boot order is: native app launches, JS bundle loads, root component renders, then real native UI appears — native always comes first.
  • The exact API shape can evolve; the conceptual order — native before JS, JS registration before rendering — is the stable part worth remembering.

Check yourself

4 questions · pass 3/4 to unlock Components and Hooks, the Same as React

up to 50
  1. 1.In a bare React Native CLI project, what are the ios/ and android/ folders?

  2. 2.Conceptually, what does AppRegistry.registerComponent (or its Expo-managed equivalent) do?

  3. 3.What is the practical role of a bundler configuration like metro.config.js in a React Native project?

  4. 4.Which best describes the correct order of a React Native app's boot sequence?

4 left to answer