Expo Router: Modern React Native Navigation Guide for 2026

Expo Router: File-Based Navigation for React Native Apps

React Native navigation has always been complicated. React Navigation requires manual route registration, nested navigator configuration, and TypeScript types that don’t stay in sync with your actual screens. Expo Router solves this by bringing file-based routing to React Native — the same pattern that made Next.js navigation effortless. Therefore, this guide covers everything from basic setup to advanced patterns like typed routes, deep linking, and authentication flows.

Why File-Based Routing Changes Everything

In React Navigation, adding a new screen requires three steps: create the component, register it in a navigator, and add TypeScript types for the route params. With Expo Router, you create a file in the app/ directory and it automatically becomes a route. The file path is the URL path. No registration, no manual type definitions, no navigator configuration files.

Moreover, Expo Router provides universal deep linking out of the box. Every screen in your app has a URL that works on iOS, Android, and web. Users can share links to specific screens, push notifications can navigate to any route, and your app handles incoming URLs without any additional configuration. This was possible with React Navigation but required significant setup for each route.

app/
├── _layout.tsx          → Root layout (tab/stack navigator)
├── index.tsx            → / (home screen)
├── settings.tsx         → /settings
├── profile/
│   ├── _layout.tsx      → Nested layout for profile section
│   ├── index.tsx        → /profile
│   └── [id].tsx         → /profile/123 (dynamic route)
├── (tabs)/
│   ├── _layout.tsx      → Tab navigator layout
│   ├── home.tsx         → Tab: Home
│   ├── search.tsx       → Tab: Search
│   └── account.tsx      → Tab: Account
├── (auth)/
│   ├── _layout.tsx      → Auth group layout
│   ├── login.tsx        → /login
│   └── register.tsx     → /register
└── [...missing].tsx     → 404 catch-all route

The file structure above creates a complete navigation system: tab navigation, stack navigation, dynamic routes, authentication flows, and a 404 handler — all from the file system. No createStackNavigator, no NavigationContainer, no route registration.

Expo Router React Native mobile app navigation
File-based routing eliminates manual route registration — create a file, get a route

Layouts: Stack, Tabs, and Drawers

Expo Router uses _layout.tsx files to define how child routes are rendered. Each layout wraps its child screens with a navigator — Stack, Tabs, or Drawer. This replaces React Navigation’s nested navigator pattern with a cleaner, co-located approach.

// app/_layout.tsx — Root layout with Stack navigator
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Screen name="(tabs)" />
      <Stack.Screen name="(auth)" />
      <Stack.Screen
        name="modal"
        options={{ presentation: 'modal' }}
      />
    </Stack>
  );
}

// app/(tabs)/_layout.tsx — Tab navigator
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';

export default function TabLayout() {
  return (
    <Tabs screenOptions={{
      tabBarActiveTintColor: '#007AFF',
      headerShown: true
    }}>
      <Tabs.Screen
        name="home"
        options={{
          title: 'Home',
          tabBarIcon: ({ color, size }) =>
            <Ionicons name="home" size={size} color={color} />
        }}
      />
      <Tabs.Screen
        name="search"
        options={{
          title: 'Search',
          tabBarIcon: ({ color, size }) =>
            <Ionicons name="search" size={size} color={color} />
        }}
      />
      <Tabs.Screen
        name="account"
        options={{
          title: 'Account',
          tabBarIcon: ({ color, size }) =>
            <Ionicons name="person" size={size} color={color} />
        }}
      />
    </Tabs>
  );
}

Route groups (parentheses folders like (tabs) and (auth)) organize routes without affecting the URL. The (tabs) group creates tab navigation but the URLs are /home, /search, /account — not /tabs/home. This separation of navigation structure from URL structure is one of Expo Router’s most powerful features.

Typed Routes and Navigation

Expo Router generates TypeScript types automatically from your file structure. When you navigate to a route, TypeScript validates both the route path and the required parameters. Consequently, typos in route names and missing parameters become compile-time errors instead of runtime crashes.

// app/profile/[id].tsx — Dynamic route with typed params
import { useLocalSearchParams, Link, router } from 'expo-router';

export default function ProfileScreen() {
  // TypeScript knows 'id' exists because the file is [id].tsx
  const { id } = useLocalSearchParams<{ id: string }>();

  return (
    <View>
      <Text>Profile: {id}</Text>

      {/* Type-safe Link — IDE autocompletes route paths */}
      <Link href="/settings">Settings</Link>

      {/* Dynamic route with params */}
      <Link href={`/profile/${userId}`}>
        View Profile
      </Link>

      {/* Programmatic navigation */}
      <Pressable onPress={() => {
        router.push({
          pathname: '/profile/[id]',
          params: { id: '456' }
        });
      }}>
        <Text>Navigate</Text>
      </Pressable>

      {/* Replace current screen (no back button) */}
      <Pressable onPress={() => router.replace('/login')}>
        <Text>Logout</Text>
      </Pressable>
    </View>
  );
}

// Enable typed routes in app.json
// { "expo": { "experiments": { "typedRoutes": true } } }

Additionally, useLocalSearchParams returns values that persist across re-renders (unlike useSearchParams which re-renders on every navigation). This distinction matters for performance — use useLocalSearchParams for screen-level params and useGlobalSearchParams only when you need to react to changes from other screens.

Mobile app typed navigation and deep linking
Typed routes catch navigation errors at compile time — not runtime

Authentication Flows and Route Protection

Protecting routes in Expo Router uses a redirect pattern in the root layout. Instead of conditional navigator rendering (the React Navigation approach), you check authentication state and redirect unauthenticated users to the login screen.

// app/_layout.tsx — Auth-protected root layout
import { Slot, useRouter, useSegments } from 'expo-router';
import { useAuth } from '../contexts/AuthContext';
import { useEffect } from 'react';

export default function RootLayout() {
  const { user, isLoading } = useAuth();
  const router = useRouter();
  const segments = useSegments();

  useEffect(() => {
    if (isLoading) return;

    const inAuthGroup = segments[0] === '(auth)';

    if (!user && !inAuthGroup) {
      // Not logged in, redirect to login
      router.replace('/login');
    } else if (user && inAuthGroup) {
      // Logged in but on auth screen, redirect to home
      router.replace('/home');
    }
  }, [user, isLoading, segments]);

  if (isLoading) return <LoadingScreen />;

  return <Slot />;
}

This pattern is simpler and more predictable than conditionally rendering different navigators. The navigation structure is always the same — the layout just redirects based on auth state. Furthermore, deep links work correctly with this pattern: if a logged-out user opens a deep link to /profile/123, they get redirected to login and then back to the profile after authentication.

Migrating from React Navigation

Migration from React Navigation to Expo Router can be done incrementally. Start by converting your app entry point and root navigator, then migrate screens one at a time. The key mapping: createStackNavigator becomes a _layout.tsx with <Stack>, createBottomTabNavigator becomes a (tabs)/_layout.tsx with <Tabs>, and navigation.navigate('Screen') becomes router.push('/screen').

Most React Navigation screen options translate directly — headerTitle, headerStyle, tabBarIcon, and presentation all work the same way. However, custom navigators (like drawer with custom content) require more adaptation. The Expo Router team provides a migration guide with detailed mapping for every React Navigation API.

React Native app development and migration
Migrate from React Navigation incrementally — one navigator at a time

Related Reading:

Resources:

In conclusion, Expo Router brings the simplicity of file-based routing to React Native. It eliminates boilerplate navigator configuration, provides automatic deep linking, generates TypeScript types from your file structure, and simplifies authentication flows. If you’re starting a new React Native project or modernizing an existing one, Expo Router is the recommended navigation solution.

Scroll to Top