React Native course Β· Module 3: Navigation

Expo Router - Modern Navigation

4 min read Β·
In this lesson10

Expo Router is a new, revolutionary file-based navigation system - similar to Next.js for React Native!

Why Expo Router?

  • File-based routing - folder structure = navigation structure
  • Automatic Deep Linking - no additional configuration needed
  • Typed routes - full TypeScript support
  • Web support - works on web like Next.js
  • Lazy loading - screens loaded on demand

Installation

# New project with Expo Router
npx create-expo-app@latest --template tabs

# Or adding to an existing project
npx expo install expo-router expo-linking expo-constants

Configuration

// app.json
{
  "expo": {
    "scheme": "atlantis",
    "web": {
      "bundler": "metro"
    },
    "plugins": ["expo-router"]
  }
}
// package.json
{
  "main": "expo-router/entry"
}

File Structure = Routing

app/
β”œβ”€β”€ _layout.tsx          # Root layout
β”œβ”€β”€ index.tsx            # / (home page)
β”œβ”€β”€ about.tsx            # /about
β”œβ”€β”€ settings/
β”‚   β”œβ”€β”€ _layout.tsx      # Layout for /settings/*
β”‚   β”œβ”€β”€ index.tsx        # /settings
β”‚   └── profile.tsx      # /settings/profile
β”œβ”€β”€ user/
β”‚   └── [id].tsx         # /user/123 (dynamic parameter)
β”œβ”€β”€ (tabs)/
β”‚   β”œβ”€β”€ _layout.tsx      # Tab Navigator
β”‚   β”œβ”€β”€ home.tsx         # Home tab
β”‚   └── search.tsx       # Search tab
└── +not-found.tsx       # 404 page

Root Layout

// app/_layout.tsx
import { Stack } from 'expo-router';
import { ThemeProvider } from '@/providers/ThemeProvider';

export default function RootLayout() {
  return (
    <ThemeProvider>
      <Stack
        screenOptions={{
          headerStyle: { backgroundColor: '#0a2e4a' },
          headerTintColor: '#fff',
        }}
      >
        <Stack.Screen name="index" options={{ title: 'Atlantis' }} />
        <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
        <Stack.Screen name="user/[id]" options={{ title: 'Profile' }} />
      </Stack>
    </ThemeProvider>
  );
}

Home Page

// app/index.tsx
import { View, Text, StyleSheet } from 'react-native';
import { Link } from 'expo-router';

export default function Home() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome to Atlantis!</Text>

      {/* Link as a component */}
      <Link href="/about" style={styles.link}>
        About the app
      </Link>

      {/* Link with parameters */}
      <Link
        href={{
          pathname: '/user/[id]',
          params: { id: '123' }
        }}
        style={styles.link}
      >
        My profile
      </Link>

      {/* Link to Tab Navigator */}
      <Link href="/(tabs)/home" style={styles.link}>
        Go to tabs
      </Link>
    </View>
  );
}

Dynamic Routes

// app/user/[id].tsx
import { useLocalSearchParams, useGlobalSearchParams, Stack } from 'expo-router';

export default function UserProfile() {
  // Get parameter from URL
  const { id } = useLocalSearchParams<{ id: string }>();

  return (
    <View>
      {/* Dynamic title */}
      <Stack.Screen options={{ title: `User #${id}` }} />

      <Text>User profile: {id}</Text>
    </View>
  );
}

Tab Navigator

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

export default function TabLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: '#00d4ff',
        tabBarStyle: { backgroundColor: '#0a2e4a' },
      }}
    >
      <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>
  );
}

Programmatic Navigation

import { router } from 'expo-router';

function NavigationButtons() {
  return (
    <View>
      {/* Navigate to a path */}
      <Pressable onPress={() => router.push('/about')}>
        <Text>About the app</Text>
      </Pressable>

      {/* Navigate with parameters */}
      <Pressable onPress={() => router.push({
        pathname: '/user/[id]',
        params: { id: '456' }
      })}>
        <Text>Profile 456</Text>
      </Pressable>

      {/* Replace (no going back) */}
      <Pressable onPress={() => router.replace('/login')}>
        <Text>Log out</Text>
      </Pressable>

      {/* Go back */}
      <Pressable onPress={() => router.back()}>
        <Text>Go back</Text>
      </Pressable>

      {/* Check if you can go back */}
      {router.canGoBack() && (
        <Pressable onPress={() => router.back()}>
          <Text>Go back</Text>
        </Pressable>
      )}
    </View>
  );
}

Groups (Hidden from URL)

// Groups in parentheses don't appear in the URL
// app/(auth)/login.tsx β†’ /login
// app/(tabs)/home.tsx β†’ /home (not /(tabs)/home)

// app/(auth)/_layout.tsx
import { Stack } from 'expo-router';

export default function AuthLayout() {
  return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Screen name="login" />
      <Stack.Screen name="register" />
    </Stack>
  );
}

Expo Router is the future of navigation in React Native. It greatly simplifies routing and automatically handles Deep Linking!

Code for this lesson: App.tsx
// File: app/_layout.tsx (Expo Router - file-based routing)
import React, { useState } from 'react';
import { Stack, Redirect } from 'expo-router';

// Simple hook simulating the user's signed-in state
function useAuth() {
  const [isSignedIn] = useState(false);
  return { isSignedIn };
}

export default function RootLayout() {
  const { isSignedIn } = useAuth();

  // TODO: if the user is NOT signed in, redirect to the login screen:
  // if (!isSignedIn) {
  //   return <Redirect href="/login" />;
  // }

  // TODO: return a <Stack> that hides the header for the (tabs) group:
  // <Stack>
  //   <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
  // </Stack>

  return null;
}

Docs entries used in this lesson

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. What is file-based routing in Expo Router?

  2. 2. What does the file name [id].tsx mean in Expo Router?

These are 2 of 4 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Vertical ordering

    Arrange layout nesting starting from the root:

  • Code editor

    Complete the _layout.tsx code.

  • Horizontal ordering

    Arrange retrieving parameters in Expo Router:

Useful articles