Skip to main content

Command Palette

Search for a command to run...

Expo Router vs React Navigation - Which One Should You Use in 2026?

Updated
•20 min read

What are We Gonna Study?

Hey there folks, hope you are doing great in your life and enjoying every bit of it!

Today in this video we are going to talk about 2 kings of the navigation realm in React Native/Expo Ecosystem, and we'll also discuss which one to use.

So, we'll end all of this debate right here šŸ˜„

  1. What routing means in mobile applications

  2. Why navigation is important in React Native apps

  3. Brief history of React Navigation

  4. Problems developers faced with traditional navigation setup

  5. Why Expo Router was introduced

  6. File-based routing explained simply

  7. Nested layouts and shared layouts in Expo Router

  8. Protected routes and authentication flows

  9. Performance comparison:

    • Bundle behavior

    • Navigation transitions

    • Developer workflow

  10. Developer Experience (DX) comparison

  11. Scalability comparison for large applications

  12. Real-world app folder structure examples

  13. Which approach companies and teams prefer

  14. When NOT to use Expo Router

  15. Situations where React Navigation still makes more sense

These are topics on our agenda today, pretty much but pretty fun and interesting as well :)

Just bear with me till the end of this blog, it will take just 10-15 minutes of read, and you'll find a lot of gain in your knowledge, so without wasting any further time, let's begin!


Basic Introduction to Navigation

Problem

Until now for our small projects, what we have done is that we have mainted a state.

Whenever someone clicks a button or something, we changed the state and based on our conditions that screen rendered right?

But tell me one thing what if we have 100 or even just 10 screens, how will you maintain it? Will you create that many screens? Wouldn't that be very complex?


Solution

Turns out, we need some kind of a thing that tells each and every screen that when they should appear or when they should disappear.

That is where your navigation libraries like React Navigation and Expo Router come into the picture.

Navigation simply means moving from one screen to another.


What Navigation Actually Means in React Native?

  • React Native's job is to render JavaScript as native UI components.

  • But it delegates the question of which screens to show, when to show them, and how to animate between them entirely to you (or to a library you bring in).

A navigation library is responsible for:

  • The navigator stack: A data structure that tracks which screens are active and in what order, so pressing "back" always returns you to the correct place.

  • Screen transitions: platform-appropriate animations: slides, fades, modal presentations.

  • Deep linking: mapping a URL or notification payload to a specific screen configuration.

  • Tab bars and drawers: persistent navigation chrome that stays visible while content changes.

  • Authentication guards: preventing unauthenticated users from reaching protected screens.


React Navigation: The Long-Time Standard

History of React Navigation

  • React Navigation emerged around 2017 as the community's answer to native navigation in React Native.

  • Before it, the options were either React Native's built-in navigator (which was later removed from the core) or third-party wrappers around native navigation stacks that were notoriously difficult to configure.

  • React Navigation took a different approach: implement navigation entirely in JavaScript, with smooth performance through React Native's animated API and gesture handler library.

  • This meant developers could configure everything in JavaScript.

  • By 2020, it had become the de facto standard.

  • The v5 release in 2020 brought a hooks-based API that felt much more natural to modern React development.

  • The v6 release in 2021 refined this further.

  • Today, React Navigation is installed in the vast majority of serious React Native projects.

React Native originally shipped with its own NavigatorIOS and later Navigator components. Both were deprecated and removed, leaving the ecosystem to build its own solutions. React Navigation was the one that won.


How React Navigation Works?

The core mental model of React Navigation is imperative composition.

You define your navigators in code, register your screens with those navigators, and navigate between them programmatically.

Here's what a typical setup looks like:

// App.tsx — traditional React Navigation setup
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();

function HomeTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Feed" component={FeedScreen} />
      <Tab.Screen name="Profile" component={ProfileScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Login" component={LoginScreen} />
        <Stack.Screen name="Home" component={HomeTabs} />
        <Stack.Screen name="ProductDetail" component={ProductDetailScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Notice what's happening here:

  • Every screen is explicitly registered.

  • Every navigator is explicitly composed.

  • The entire navigation structure lives in one place (usually App.tsx).

  • This is both a strength and, as we'll see, an eventual pain point.


Problems with React Navigation

For smaller apps, React Navigation's approach works well.

But as applications grow, cracks appear.

Developers who've shipped large React Native apps tend to describe the same recurring frustrations.

Single Navigation File

  • In a typical production app, your navigation configuration might register 40, 60, or 100+ screens across nested stacks, tabs, and drawers.

  • They all live in one file, or get split across several files that import each other in complex chains.

  • A new team member who needs to understand "what happens when a user taps this button" has to trace navigation calls through layers of abstraction, cross-reference screen names (which are just strings, prone to typos), and reconstruct the app's structure mentally.

String-based screen names

  • React Navigation's core navigation call looks like navigation.navigate('ProductDetail', { id: product.id }).

  • That 'ProductDetail' is a plain string.

  • TypeScript can be configured to give you type safety here, but it requires boilerplate, defining a route params type, augmenting the navigation types.

  • It's doable, but nobody finds it intuitive, and many teams skip it under deadline pressure, shipping apps full of stringly-typed navigation calls.

  • With React Navigation, your deep link URL structure is entirely separate from your screen structure.

  • You define your screens in JavaScript. Then you separately define your deep link configuration.

  • These can silently drift out of sync. Adding a new screen means updating both the navigator and the linking config.

Auth flows require manual conditional rendering

  • Showing different navigator trees to authenticated vs unauthenticated users is a common pattern but one React Navigation doesn't enforce or formalize.

  • The community has settled on conditional rendering of entire navigator trees based on auth state, but this means auth logic leaks into your navigation configuration, and it's easy to create race conditions or flicker between the wrong navigator.


Expo Router: The Cherry on Cake

More History ;)

  • Expo Router was introduced in 2022 as part of the Expo ecosystem, with a stable v1 release in late 2022 and major iterations since.

  • The core insight it brought was one that web developers would immediately recognize: the file system can be the source of truth for your routes.

Expo Router is not a replacement for React Navigation. It's a file-system-based routing layer built on top of React Navigation.


How it Works?

  • Expo Router internally uses React Navigation.

  • When you use Expo Router, you still get React Navigation's navigators, stack animations, gesture handling, and deep link infrastructure.

  • What changes is how you configure all of that. Instead of writing imperative navigator compositions in TypeScript, you create files in an app/ directory.

  • Expo Router reads that directory and generates the navigation configuration automatically.

The idea is from web frameworks particularly Next.js, which popularized file-based routing for web applications. If you've used Next.js, Expo Router's model will feel immediately familiar.


File Based Routing Masterclass

  • The simplest way to understand Expo Router's model: a file's location in the app/ folder is its URL.

  • A file at app/login.tsx becomes the /login route.

  • A file at app/products/[id].tsx becomes /products/123 for any product ID.

  • A file at app/index.tsx is the root route, /.

  • Special file names carry special meaning. __layout.tsx defines a layout that wraps all sibling and child routes, this is how you create persistent UI like tab bars, headers, and drawers.

  • Folders wrapped in parentheses like (tabs)/ are "route groups", they organize files without adding to the URL.

Here are the examples of some files and their routes

app/
ā”œā”€ā”€ __layout.tsx                 # Root layout (NavigationContainer)
ā”œā”€ā”€ index.tsx                    # Route: /
ā”œā”€ā”€ login.tsx                    # Route: /login
ā”œā”€ā”€ +not-found.tsx               # 404 screen
│
ā”œā”€ā”€ (tabs)/                      # Route group (not added to URL)
│   ā”œā”€ā”€ __layout.tsx             # Creates the tab navigator
│   ā”œā”€ā”€ feed.tsx                 # Route: /feed
│   ā”œā”€ā”€ profile.tsx              # Route: /profile
│   └── settings.tsx             # Route: /settings
│
└── products/
    ā”œā”€ā”€ __layout.tsx             # Stack layout for products
    ā”œā”€ā”€ index.tsx                # Route: /products
    ā”œā”€ā”€ [id].tsx                 # Route: /products/:id
    │
    └── [id]/
        └── reviews.tsx          # Route: /products/:id/reviews

Navigation happens via the router object or the Link component, both of which accept URLs rather than screen name strings:

// Expo Router navigation — type-safe, URL-based
import { router, Link } from 'expo-router';

// Imperative navigation
router.push('/products/42');
router.replace('/login');
router.back();

// Declarative Link component (like <a> on the web)
<Link href="/products/42">View Product</Link>

// With typed params — fully inferred from your file structure
router.push({ pathname: '/products/[id]', params: { id: '42' } });

Here is the flow chart of how the folder structure looks here:

Here is the difference of React Navigation and Expo Router:


Nested Layouts and Shared Layouts

  • One of Expo Router's most powerful features is its layout system, which directly mirrors how Next.js handles shared UI.

  • A __layout.tsx file wraps all routes in its directory. When you navigate between siblings (e.g., between /feed and /profile), the parent layout stays mounted, the tab bar doesn't re-render, your data doesn't reload, your scroll position can be preserved.

  • This is fundamentally different from how most developers initially think about mobile navigation.

  • The mental model shifts from "replace the screen" to "update the content while the chrome persists."

  • Web developers will recognize this immediately as the same model that makes browser tab bars and sidebars feel seamless.

// app/(tabs)/_layout.tsx — Tab navigator as a layout
import { Tabs } from 'expo-router';

export default function TabLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: '#7c6af7',
        headerShown: false,
      }}
    >
      {/* No need to list screens — auto-discovered from files */}
      <Tabs.Screen
        name="feed"
        options={{ title: 'Feed', tabBarIcon: ... }}
      />
    </Tabs>
  );
}

Here is the diagram represneting the hierarchy of the nested layouts:


Expo Router VS React Navigation

Protected Routes and Authentication Flows

  • Authentication is where many navigation setups become genuinely messy.

  • The goal is straightforward: unauthenticated users should only see login/onboarding screens.

  • Authenticated users should go straight to the app.

  • A user who loses their session mid-session should be redirected to login, then back to where they were.

  • In React Navigation, the conventional approach is to conditionally render different navigator trees based on auth state:

// React Navigation auth pattern — conditional navigator trees
export default function App() {
  const { isAuthenticated } = useAuth();

  return (
    <NavigationContainer>
      {isAuthenticated
        ? <AppStack />
        : <AuthStack />
      }
    </NavigationContainer>
  );
}

This works, but it means swapping entire navigator trees when auth state changes which can cause jarring transitions or white flashes. It also puts auth logic firmly in the navigation layer.

Expo Router uses a redirect-based model that feels more web-like. You use the Redirect component or the useRouter hook inside layouts to redirect users before the screen renders:

// app/(tabs)/_layout.tsx — protected tab group
import { Redirect } from 'expo-router';
import { useAuth } from '../context/auth';

export default function TabLayout() {
  const { session, isLoading } = useAuth();

  if (isLoading) return <SplashScreen />;

  // Redirect before rendering any tab content
  if (!session) return <Redirect href="/login" />;

  return <Tabs>...</Tabs>;
}

Here is another diagram that shows the flow of authentication routes:


Performance Comparison

Bundle behavior

  • Since Expo Router builds on React Navigation, the underlying navigation runtime performance is identical.

  • You're using the same navigators, the same gesture handler library, and the same Reanimated-driven transitions.

  • Where they diverge is bundle structure. Expo Router enables automatic code splitting, each file in your app/ folder becomes a lazily-loaded chunk.

  • A user who never visits /settings never loads that screen's code.

  • With React Navigation, all your screens are imported in the navigator configuration, meaning they load upfront (unless you manually implement lazy imports, which most teams don't).

  • For large applications, this matters, An e-commerce app with 80 screens doesn't need all 80 loaded on app start.

  • Expo Router gets you lazy loading for free. React Navigation requires you to opt in explicitly.


  • At runtime, both use the same transition engine.

  • The screen animations you see with createStackNavigator in React Navigation are the same animations you see when you push a new route in Expo Router.

  • No difference here.


Developer workflow

  • This is where the difference is most felt day-to-day.

  • Adding a new screen with React Navigation means: create the component file, import it in the navigator, register it with a name, add TypeScript types.

  • Adding a new screen with Expo Router means: create the file in the right folder. That's it.

  • On a team shipping fast, the compound effect of that friction matters more than it initially seems.


TL;DR

Just read this diagram and you'll understand this difference:

Developer Experience: The Real Comparison

Beginner perspective

  • For someone learning React Native, Expo Router has a dramatically lower cognitive load.

  • The mental model, "a file is a screen, a folder is a section" is intuitive and maps to how beginners already think about file organization.

  • You don't need to understand what a NavigationContainer is, what a Stack.Navigator does, or why you need to name your screens as strings.

  • React Navigation, by contrast, requires understanding several abstractions before you can do anything: navigators, screens, the navigation prop, params, the linking config.

  • None of this is inherently hard, but it's a lot to absorb when you're also learning React Native itself.


Team scalability

  • At the team level, the self-documenting nature of file-based routing becomes a significant advantage.

  • When a new engineer joins the team and wants to understand the app's structure, they can navigate the app/ folder.

  • The directory listing is the app map.

  • There's no separate navigation file to maintain or keep in sync.

  • With React Navigation, the navigation configuration file tends to become a source of merge conflicts, since it's the single place where all screen registrations happen.

  • Two engineers adding screens on separate branches will always touch the same file.


Enterprise maintainability

  • Large teams at scale tend to reach for whichever solution gives them the best combination of explicitness and automation.

  • React Navigation is more explicit, every navigation relationship is declared in code, which some senior engineers prefer for auditability.

  • Expo Router provides more automation, which reduces boilerplate but means some configuration is implicit rather than explicit.

  • Both approaches are viable at enterprise scale.

  • The teams that seem happiest with Expo Router at scale are those who invest in understanding its conventions deeply, rather than trying to work around them.

  • The teams happiest with React Navigation at scale are those who've invested in good TypeScript tooling and clear conventions for the navigation config.


Real-World App Folder Structure

Here's what a production-grade e-commerce app might look like with Expo Router, compared to the equivalent React Navigation setup.

app/
ā”œā”€ā”€ __layout.tsx                  # Root providers: auth, theme, query
ā”œā”€ā”€ +not-found.tsx               # 404 for deep links
ā”œā”€ā”€ index.tsx                    # Splash / redirect logic
│
ā”œā”€ā”€ (auth)/                      # Unauthenticated screens
│   ā”œā”€ā”€ __layout.tsx              # Auth stack configuration
│   ā”œā”€ā”€ login.tsx                # /login
│   ā”œā”€ā”€ register.tsx             # /register
│   └── forgot-password.tsx      # /forgot-password
│
ā”œā”€ā”€ (app)/                       # Authenticated screens
│   ā”œā”€ā”€ __layout.tsx              # Auth guard + tab navigator
│   │
│   ā”œā”€ā”€ (tabs)/                  # Bottom tab navigation
│   │   ā”œā”€ā”€ __layout.tsx          # Tab bar configuration
│   │   ā”œā”€ā”€ index.tsx            # /home
│   │   ā”œā”€ā”€ explore.tsx          # /explore
│   │   ā”œā”€ā”€ cart.tsx             # /cart
│   │   └── account.tsx          # /account
│   │
│   ā”œā”€ā”€ products/
│   │   ā”œā”€ā”€ __layout.tsx          # Product stack navigation
│   │   ā”œā”€ā”€ [id].tsx             # /products/:id
│   │   └── [id]/
│   │       └── reviews.tsx      # /products/:id/reviews
│   │
│   ā”œā”€ā”€ orders/
│   │   ā”œā”€ā”€ index.tsx            # /orders
│   │   └── [orderId].tsx        # /orders/:orderId
│   │
│   └── modals/
│       ā”œā”€ā”€ filter.tsx           # Filter modal overlay
│       └── address-picker.tsx   # Address picker modal overlay

With React Navigation, this same structure requires a navigation file that explicitly composes every one of these navigators and registers every one of these screens by name. That file would be ~150-200 lines of boilerplate for an app of this size.


Architecture Comparison

Here is another diagram so that you can understand the architectural comparison between them


Which Teams Choose What

New Expo-based projects nearly universally reach for Expo Router. The Expo team has made it the default choice when you generate a new project with npx create-expo-app. For teams starting fresh and not already invested in React Navigation patterns, there's little reason to choose otherwise.

Existing React Native projects (especially those using the React Native CLI rather than Expo) tend to stay with React Navigation. Migration isn't trivial — it requires restructuring your entire app folder and rethinking your navigation model. Most teams with a working navigation setup prefer to leave it alone.

Bare React Native projects (not using the Expo managed workflow) typically use React Navigation directly. Expo Router requires the Expo ecosystem, specifically the Expo dev tools and build system. It can technically run in a bare React Native project via the Expo bare workflow, but the setup is considerably more involved.

Enterprise teams at larger companies are often on React Navigation simply due to when their project was started. The navigation choice, once made, tends to be long-lived. Many teams that started in 2018–2021 are on React Navigation v6 and have no pressing reason to change.

Teams building universal apps (same codebase for iOS, Android, and web) are among the biggest Expo Router enthusiasts. Because Expo Router maps to URLs natively, it produces real, linkable web URLs when targeting the web, while the same code produces native stack navigations on mobile. This is hard to replicate with plain React Navigation.


When NOT to Use Expo Router

Expo Router is an excellent choice for most new projects, but it's not the right tool in every situation.

You're not using Expo

  • If your project uses React Native CLI directly, or if your company's build pipeline doesn't support Expo, Expo Router is off the table without significant infrastructure changes.

  • React Navigation works seamlessly in any React Native environment.


You need deeply custom navigation behavior

  • Expo Router's layout API is powerful but opinionated.

  • If you need a completely custom transition engine, unconventional navigator composition (e.g., a stack nested inside a drawer nested inside a modal nested inside a tab, with unusual gesture configurations), or navigation patterns that don't map well to URL hierarchies, you may find yourself fighting Expo Router's abstractions.

  • React Navigation gives you full programmatic control.


Your team has deep React Navigation expertise

  • Switching costs are real.

  • If your team has spent years developing patterns, tooling, and mental models around React Navigation, the productivity gain of adopting Expo Router needs to outweigh the learning curve.

  • For a team that is already highly effective with React Navigation, the answer is often "not now."


You need Expo Router features that aren't fully stable

  • Expo Router is mature for core use cases but still evolving.

  • Features like server-side rendering for React Native Web, API routes in mobile apps, and some advanced layout configurations are newer and may carry rough edges.

  • React Navigation, being older, has had more time to stabilize edge cases.


Migrating a large production app from React Navigation to Expo Router is a significant undertaking. It typically requires restructuring your entire src/ folder, rewriting all navigation calls, reconsidering your auth flow architecture, and re-testing every deep link. For apps beyond a certain size, it may only be worth doing as part of a larger rewrite.


When React Navigation Makes More Sense?

Despite the momentum behind Expo Router, React Navigation remains the right choice in several specific contexts.

Bare React Native projects

  • If you've ejected from Expo or are using React Native CLI, React Navigation integrates more cleanly.

  • Adding Expo Router to a bare project is possible but adds Expo's module system as a dependency, which may not fit every project's constraints.


Apps with non-URL-shaped navigation

  • Not all app navigation maps naturally to a URL hierarchy.

  • Games with complex state-driven navigation, apps where screens are determined by API responses rather than user intent, or highly experimental UI patterns may benefit from React Navigation's more imperative, flexible model.


Teams that have already solved the boilerplate problem

  • Many experienced React Navigation shops have codegen scripts, snippets, and conventions that make adding a new screen nearly as fast as creating a file.

  • If your team has already solved the friction problem, the switch may bring fewer benefits than advertised.


When you need to precisely control what's in the bundle

  • In some performance-critical scenarios, you might want more explicit control over lazy loading than Expo Router provides by default.

  • React Navigation lets you precisely decide which screens load eagerly vs lazily, at the cost of more boilerplate.


Wrap Up!

Wait wait, drink some water that was a lot for today, wasn't that? šŸ˜„

Basically React Navigation is the old standard that still works today, if you need freedom you should go with it.

Expo Router is fast, DX is awesome but takes away some of your freedom.

The correct choice depends on your project requirements.

And with this, let's end this blog right here, I'll catch you up in the next blog, until then keep coding, keep learning about such interesting concepts, and keep enjoying your life too!