Module 6: Navigation with Expo Router
Navigation Concepts
Understanding how users move through mobile apps before we build it
๐ฏ Learning Objectives
- Understand why mobile navigation differs fundamentally from web navigation
- Learn the four core navigation patterns: Stack, Tab, Drawer, and Modal
- Visualize the navigation stack and how screens layer on top of each other
- Recognize platform-specific navigation conventions for iOS and Android
- Understand navigation state and how it drives what users see
- Know why navigation libraries exist and what problems they solve
- Preview the navigation architecture we'll build throughout this module
๐ Table of Contents
- Web vs Mobile Navigation
- Stack Navigation: The Foundation
- Tab Navigation: Parallel Destinations
- Drawer Navigation: Hidden Menus
- Modal Navigation: Focused Interactions
- Combining Navigation Patterns
- Navigation State: The Source of Truth
- Platform Conventions
- Why Navigation Libraries?
- Preview: Expo Router
- Hands-On Exercises
- Summary
Web vs Mobile Navigation
If you're coming from web development, you need to fundamentally rewire how you think about navigation. On the web, navigation is simple: URLs map to pages, the browser handles the back button, and every page exists independently. Mobile is differentโdramatically different.
๐ The Core Difference
Web navigation is URL-based and stateless. Mobile navigation is state-based and hierarchical. On the web, you navigate BY changing the URL. On mobile, you navigate BY manipulating a stack of screens, each maintaining its own state.
Let's visualize this mental shift:
This difference has profound implications for how you build apps:
๐ Key Differences
| Aspect | Web | Mobile |
|---|---|---|
| Navigation trigger | URL change | Stack manipulation |
| Previous screen | Destroyed (usually) | Preserved in memory |
| Screen state | Lost unless saved | Maintained while in stack |
| Back button | Browser history | Pops current screen off stack |
| Animations | Page reload (or SPA fade) | Native slide/fade transitions |
| Deep linking | Natural (URLs exist) | Requires configuration |
๐ก The SPA Connection
If you've built Single Page Applications (SPAs) with React Router, you're closer to mobile navigation than traditional web developers. SPAs also manage navigation state in JavaScript rather than relying solely on the browser. However, even SPAs typically don't preserve previous "pages" the way mobile doesโthey mount and unmount components as routes change.
Why Mobile Navigation Is Stateful
Mobile navigation evolved this way for good reasons:
- Limited screen real estate: You can only show one "page" at a time, so you need a system to manage what's visible
- Touch-based interaction: Gestures like swiping to go back require screens to exist (you're literally dragging the current screen aside)
- Memory and performance: Creating/destroying complex UIs is expensive; keeping screens in memory enables instant back navigation
- User expectations: When users go back, they expect to see exactly what they left, not a fresh reload
This statefulness is both powerful and challenging. It enables fluid, native-feeling navigation, but it also means you need to think carefully about screen lifecycle, memory usage, and state management.
Combining Navigation Patterns
Real apps don't use just one navigation patternโthey combine multiple patterns to create intuitive, powerful navigation architectures. Understanding how patterns nest is crucial for building complex apps.
flowchart TB
subgraph App["App Navigation Architecture"]
subgraph Tabs["Tab Navigator"]
subgraph HomeStack["Home Stack"]
H1[Home Feed] --> H2[Post Detail]
H2 --> H3[Comments]
H2 --> H4[User Profile]
end
subgraph SearchStack["Search Stack"]
S1[Search] --> S2[Results]
S2 --> S3[Item Detail]
end
subgraph ProfileStack["Profile Stack"]
P1[My Profile] --> P2[Edit Profile]
P1 --> P3[Settings]
P3 --> P4[Privacy]
end
end
Modal1[Compose Post Modal]
Modal2[Login Modal]
end
HomeStack -.-> Modal1
SearchStack -.-> Modal1
ProfileStack -.-> Modal2
style Modal1 fill:#fff3cd,stroke:#ffc107
style Modal2 fill:#fff3cd,stroke:#ffc107
In this architecture:
- Tabs provide the top-level navigation structure
- Stacks within each tab handle hierarchical navigation
- Modals can be triggered from anywhere for specific tasks
Nesting Rules
๐ How Navigators Nest
- Tabs containing Stacks: Most common pattern. Each tab has its own navigation history.
- Stack containing Tabs: Less common, but used when tabs should be "inside" the app (e.g., after login).
- Drawer containing Tabs: Enterprise apps with many features.
- Modals overlay everything: They sit above all other navigators.
๐ก Authentication Pattern
A very common pattern is having a root stack that conditionally renders either authentication screens (Login, Signup, Forgot Password) or the main app (usually tabs with stacks). When the user logs in/out, the entire navigation tree changes. We'll implement this exact pattern in Lesson 6: Authentication Flows.
Platform Conventions
iOS and Android have different navigation conventions that users expect. A well-built app respects these conventions, providing a native feel on each platform.
๐ Key Platform Differences
| Feature | iOS | Android |
|---|---|---|
| Back navigation | Swipe from left edge | Hardware/gesture back button |
| Stack animation | Slide from right | Fade or slide up |
| Modal animation | Slide up as card | Fade in or slide up |
| Tab bar style | Icons + labels, translucent | Material style, solid |
| Header style | Large titles that collapse | Fixed app bar height |
| Primary action | Text button in nav bar | FAB (Floating Action Button) |
The good news: Expo Router and React Navigation automatically apply platform-appropriate animations and styles. You don't need to manually code different behaviorsโthey're built in.
โ ๏ธ Don't Fight Platform Conventions
Resist the temptation to create a "unified" experience that ignores platform norms. iOS users expect to swipe back; Android users expect the system back button to work. Fighting these expectations creates confusion and frustration.
Platform-Specific Navigation Code
import { Platform } from 'react-native';
// Adjusting behavior per platform
const screenOptions = {
// iOS: slide from right, Android: fade
animation: Platform.select({
ios: 'slide_from_right',
android: 'fade',
}),
// iOS: large titles, Android: standard
headerLargeTitle: Platform.OS === 'ios',
// iOS: translucent header, Android: solid
headerTranslucent: Platform.OS === 'ios',
};
// Handling Android back button
import { BackHandler } from 'react-native';
import { useFocusEffect } from '@react-navigation/native';
function MyScreen() {
useFocusEffect(
useCallback(() => {
const onBackPress = () => {
// Custom back behavior
// Return true to prevent default behavior
return false;
};
// Only needed on Android
if (Platform.OS === 'android') {
BackHandler.addEventListener('hardwareBackPress', onBackPress);
return () => BackHandler.removeEventListener('hardwareBackPress', onBackPress);
}
}, [])
);
}
Why Navigation Libraries?
You might wonder: if React Native provides basic views and can respond to touches, why do we need a separate navigation library? Can't we just manage our own stack of screens?
You could build navigation from scratch, but you'd be reinventing a very complex wheel:
๐ซ What You'd Have to Build Yourself
- Screen lifecycle management (mount, focus, blur, unmount)
- Gesture handling for back swipe (iOS) and edge swipe
- Platform-specific animations with spring physics
- Hardware back button handling (Android)
- Deep link URL parsing and state hydration
- Tab state preservation across switches
- Modal presentation with proper layering
- Screen transition interruption handling
- Accessibility announcements for screen changes
- Safe area handling in headers and tab bars
- Keyboard avoiding behavior during navigation
- State persistence across app restarts
Navigation libraries solve all of this. They've been battle-tested in millions of apps and handle edge cases you'd never think of.
The React Native Navigation Landscape
Two main options dominate React Native navigation:
๐งญ React Navigation
The most popular navigation library for React Native. It's JavaScript-based, highly customizable, and works with Expo out of the box.
- Mature ecosystem with many navigators (stack, tabs, drawer, etc.)
- Deep linking support
- TypeScript support
- Active community and documentation
๐ Expo Router (Built on React Navigation)
A file-based router built on top of React Navigation. If you've used Next.js, you'll feel right at home.
- File-based routing: Create a file, get a route automatically
- Automatic deep linking: URLs derived from file structure
- TypeScript routes: Type-safe navigation
- Universal links: Works on iOS, Android, AND web
- Built on React Navigation: All its power, better DX
In this course, we'll use Expo Router because:
- It provides a more intuitive mental model (files = routes)
- Deep linking works automatically
- TypeScript integration is excellent
- It's the recommended approach for new Expo projects
- Understanding it also teaches you React Navigation concepts
flowchart TB
subgraph ExpoRouter["Expo Router"]
direction TB
ER[File-Based API]
ER --> |"Provides"| DX[Better Developer Experience]
ER --> |"Generates"| DL[Automatic Deep Links]
ER --> |"Enables"| TS[Type-Safe Navigation]
end
subgraph ReactNav["React Navigation"]
direction TB
RN[Core Navigation Engine]
RN --> |"Provides"| Stack[Stack Navigator]
RN --> |"Provides"| Tabs[Tab Navigator]
RN --> |"Provides"| Drawer[Drawer Navigator]
RN --> |"Handles"| Gestures[Gestures & Animations]
end
ExpoRouter --> |"Built On"| ReactNav
style ExpoRouter fill:#e3f2fd,stroke:#2196F3
style ReactNav fill:#e8f5e9,stroke:#4CAF50
Preview: Expo Router
Before we dive into implementation in the next lesson, let's preview what navigation looks like with Expo Router. The core idea is brilliantly simple: your file structure IS your route structure.
app/
โโโ _layout.tsx # Root layout (defines navigators)
โโโ index.tsx # "/" - Home screen
โโโ about.tsx # "/about" - About screen
โโโ (tabs)/ # Tab group
โ โโโ _layout.tsx # Tab navigator configuration
โ โโโ index.tsx # "/tabs" or first tab
โ โโโ search.tsx # "/tabs/search"
โ โโโ profile.tsx # "/tabs/profile"
โโโ product/
โ โโโ _layout.tsx # Stack for product screens
โ โโโ [id].tsx # "/product/123" - Dynamic route
โโโ (auth)/ # Auth group (parentheses = not in URL)
โโโ _layout.tsx # Auth stack
โโโ login.tsx # "/login"
โโโ signup.tsx # "/signup"
Each file automatically becomes a route. No configuration needed. Let's see what the code looks like:
// app/_layout.tsx - Root layout
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
<Stack.Screen name="product/[id]" options={{ title: 'Product' }} />
</Stack>
);
}
// app/(tabs)/_layout.tsx - Tab navigator
import { Tabs } from 'expo-router';
export default function TabLayout() {
return (
<Tabs>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color }) => <Ionicons name="home" color={color} />
}}
/>
<Tabs.Screen
name="search"
options={{
title: 'Search',
tabBarIcon: ({ color }) => <Ionicons name="search" color={color} />
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: ({ color }) => <Ionicons name="person" color={color} />
}}
/>
</Tabs>
);
}
// app/(tabs)/index.tsx - Home screen
import { View, Text, Pressable } from 'react-native';
import { Link } from 'expo-router';
export default function HomeScreen() {
return (
<View>
<Text>Welcome Home!</Text>
{/* Navigate using Link component */}
<Link href="/product/123">
View Product 123
</Link>
{/* Or with more control */}
<Link href="/search" asChild>
<Pressable>
<Text>Go to Search</Text>
</Pressable>
</Link>
</View>
);
}
// app/product/[id].tsx - Dynamic route
import { useLocalSearchParams } from 'expo-router';
export default function ProductScreen() {
const { id } = useLocalSearchParams();
return (
<View>
<Text>Product ID: {id}</Text>
</View>
);
}
โ What Expo Router Gives You
- Automatic routes: Create a file, get a route
- Automatic deep linking:
/product/123works immediately - Type-safe navigation: TypeScript knows your routes
- Web support: Same code works on web with proper URLs
- Nested layouts:
_layout.tsxfiles define structure - Groups: Parentheses
(auth)organize without affecting URLs
What We'll Build in This Module
Over the next seven lessons, you'll build a complete navigation system:
flowchart TB
subgraph Module6["Module 6: Navigation with Expo Router"]
L1[Lesson 1: Concepts] --> L2[Lesson 2: Setup]
L2 --> L3[Lesson 3: Stack Navigation]
L3 --> L4[Lesson 4: Tab Navigation]
L4 --> L5[Lesson 5: Nested Navigation]
L5 --> L6[Lesson 6: Auth Flows]
L6 --> L7[Lesson 7: Deep Linking]
L7 --> L8[Lesson 8: Advanced Patterns]
end
L8 --> Project[Module Project:
Multi-tab app with auth,
protected routes, deep links]
style L1 fill:#4CAF50,color:#fff
style Project fill:#ff9800,color:#fff
By the end of this module, you'll have built an app with:
- Tab-based main navigation
- Stack navigation within each tab
- Authentication flow with protected routes
- Dynamic routes for data-driven screens
- Working deep links that open specific screens
- Modal presentations for focused tasks
Hands-On Exercises
These exercises help solidify the navigation concepts before we start implementing.
Exercise 1: Navigation Pattern Identification
Open three of your favorite apps and identify the navigation patterns they use.
For each app, document:
- What type of primary navigation? (tabs, drawer, single stack)
- How many levels deep can you navigate?
- When do modals appear vs stack screens?
- What happens when you tap an already-active tab?
- How does the back gesture/button work?
โ Example Analysis: Instagram
Primary navigation: Bottom tabs (5 tabs: Home, Search, Reels, Shop, Profile)
Depth: Deep stacks within each tab
- Home โ Post โ Comments โ User Profile โ Their Posts โ ...
- Can go many levels deep
Modals used for:
- Creating new posts (full-screen modal with camera/gallery)
- Stories viewer
- Share sheet
- Comments (sometimes as bottom sheet)
Tab tap behavior:
- If at root: Scrolls to top
- If deep in stack: Pops to root
Back behavior:
- iOS: Swipe from left edge
- Android: System back button
- Both: Back arrow in header
Exercise 2: Design a Navigation Architecture
Design the navigation structure for a recipe app with these features:
- Browse recipes by category
- Search recipes
- View recipe details with ingredients and steps
- Save favorite recipes
- Create and edit your own recipes
- User profile with settings
- Shopping list feature
Create:
- A diagram showing the navigation structure
- Decision explanations: why tabs vs stack vs modal for each section
- The file structure if using Expo Router
โ Solution
Navigation Structure:
Root Stack
โโโ (tabs) - Bottom Tab Navigator
โ โโโ Home Stack
โ โ โโโ index (Home/Featured)
โ โ โโโ category/[id] (Category listing)
โ โ โโโ recipe/[id] (Recipe detail)
โ โ
โ โโโ Search Stack
โ โ โโโ index (Search screen)
โ โ โโโ recipe/[id] (Recipe detail)
โ โ
โ โโโ Favorites Stack
โ โ โโโ index (Saved recipes list)
โ โ โโโ recipe/[id] (Recipe detail)
โ โ
โ โโโ Shopping Stack
โ โ โโโ index (Shopping list)
โ โ
โ โโโ Profile Stack
โ โโโ index (Profile)
โ โโโ my-recipes (My recipes list)
โ โโโ recipe/[id] (View my recipe)
โ โโโ settings (Settings)
โ
โโโ (modals) - Modal group
โ โโโ create-recipe (Full-screen modal)
โ โโโ edit-recipe/[id] (Full-screen modal)
โ โโโ add-to-list (Bottom sheet modal)
โ
โโโ (auth) - Auth group (when logged out)
โโโ login
โโโ signup
Design Decisions:
- Tabs: 5 main areas (Home, Search, Favorites, Shopping, Profile) - all frequently accessed
- Stacks: Within each tab for drilling into content
- Modals: Creating/editing recipes (new content, can be canceled), adding to shopping list (quick action)
- Recipe detail shared: Same screen accessible from multiple tabs
File Structure:
app/
โโโ _layout.tsx
โโโ (tabs)/
โ โโโ _layout.tsx
โ โโโ index.tsx
โ โโโ search.tsx
โ โโโ favorites.tsx
โ โโโ shopping.tsx
โ โโโ profile/
โ โโโ _layout.tsx
โ โโโ index.tsx
โ โโโ my-recipes.tsx
โ โโโ settings.tsx
โโโ category/
โ โโโ [id].tsx
โโโ recipe/
โ โโโ [id].tsx
โโโ create-recipe.tsx
โโโ edit-recipe/
โ โโโ [id].tsx
โโโ (auth)/
โโโ _layout.tsx
โโโ login.tsx
โโโ signup.tsx
Exercise 3: Navigation State Visualization
Given this navigation state, describe what the user would see:
{
"type": "tab",
"index": 2,
"routes": [
{
"name": "HomeTab",
"state": {
"type": "stack",
"index": 0,
"routes": [{ "name": "Home" }]
}
},
{
"name": "SearchTab",
"state": {
"type": "stack",
"index": 1,
"routes": [
{ "name": "Search" },
{ "name": "Results", "params": { "query": "pizza" } }
]
}
},
{
"name": "ProfileTab",
"state": {
"type": "stack",
"index": 2,
"routes": [
{ "name": "Profile" },
{ "name": "Settings" },
{ "name": "Privacy" }
]
}
}
]
}
Answer these questions:
- Which tab is currently active?
- What screen is the user viewing?
- What happens if the user taps "Back"?
- What happens if the user taps the "Search" tab?
- What happens if the user taps the "Home" tab?
โ Answers
- Active tab: ProfileTab (index: 2)
- Current screen: Privacy (it's at index 2 of the Profile stack)
- If user taps Back: They'll see Settings (pop Privacy off the stack)
- If user taps Search tab: They'll see the Results screen with "pizza" query (the Search tab's stack is preserved at index 1)
- If user taps Home tab: They'll see the Home screen (Home tab is at its root)
Key insight: Each tab preserves its navigation state. When the user returns to a tab, they're exactly where they left off.
Exercise 4: Platform Convention Matching
Match these behaviors to the correct platform (iOS, Android, or Both):
- Swipe from left edge to go back
- Hardware back button
- Tab bar at bottom of screen
- Large collapsible titles
- Floating Action Button (FAB)
- Pull to refresh
- Modal slides up from bottom
- Drawer activated by hamburger menu
โ Answers
- Swipe from left edge: iOS (though Android now has similar gesture navigation)
- Hardware back button: Android
- Tab bar at bottom: Both (but more emphasized on iOS)
- Large collapsible titles: iOS (iOS 11+ feature)
- Floating Action Button: Android (Material Design pattern)
- Pull to refresh: Both
- Modal slides up: Both (standard on iOS, common on Android)
- Drawer/hamburger: Both, but more common on Android
Summary
You now have a solid conceptual foundation for mobile navigation. Before writing any code, you understand the patterns, the state model, and the platform conventions that make mobile navigation work.
๐ฏ Key Takeaways
- Mobile navigation is state-based: Screens stack and persist, unlike web pages
- Four core patterns: Stack (hierarchical), Tabs (parallel), Drawer (hidden menu), Modal (focused task)
- Patterns combine: Real apps nest navigators (tabs containing stacks, modals overlaying everything)
- Navigation state is the source of truth: A serializable object that determines what renders
- Platform conventions matter: iOS and Android users have different expectations
- Libraries solve complexity: Gestures, animations, deep linkingโdon't reinvent these
- Expo Router = file-based routing: Files become routes with automatic deep linking
๐งญ The Navigation Mental Model
Think of your app as a building: Tabs are floors (parallel destinations), Stacks are hallways (sequences of rooms), and Modals are popup offices (focused spaces that demand attention before you can leave). Users move through this building, and the navigation state is the map showing exactly where they are.
What's Next?
In the next lesson, we'll set up Expo Router in a new project and explore how the file-based routing system works in practice. You'll create your first routes, navigate between screens, and see how Expo Router handles the complexity we discussed today.
Get ready to turn these concepts into working code!