React Native course Β· Module 3: Navigation
Expo Router - Modern Navigation
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-constantsConfiguration
// 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 pageRoot 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. What is file-based routing in Expo Router?
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: