# Expo Router in React Native: File-Based Navigation Complete Guide
> Master Expo Router for React Native with this complete tutorial covering file-based routing, layouts, dynamic routes, typed navigation, and advanced patterns like modals and tabs in 2026.
- Published: 2026-04-18
- Updated: 2026-04-18
- Author: SharpSkill
- Tags: react-native, expo, expo-router, navigation, mobile-development, tutorial
- Reading time: 9 min
---
Expo Router brings file-based routing to React Native, replacing manual navigation configuration with a convention-driven approach inspired by Next.js. Starting with Expo SDK 55 and Expo Router v6, building cross-platform navigation for Android, iOS, and web requires nothing more than creating files in the right directory.
> **Quick Setup**
>
> New Expo projects ship with Expo Router pre-configured. Run `npx create-expo-app@latest --template default@sdk-55` to start with file-based routing out of the box. Existing projects can add it by installing `expo-router` and updating the entry point.
## How File-Based Routing Works in Expo Router
Every file inside the `app` directory automatically becomes a route. The file path maps directly to the URL path, eliminating the need for a centralized navigation configuration. A file at `app/settings.tsx` creates a `/settings` route, while `app/profile/edit.tsx` maps to `/profile/edit`.
This approach offers three key advantages over traditional React Navigation setup:
- **Zero configuration**: routes exist the moment a file is created
- **Automatic deep linking**: every screen gets a URL, enabling sharing and testing
- **Type-safe navigation**: TypeScript knows which routes exist at compile time
```typescript
// app/index.tsx
import { View, Text, StyleSheet } from 'react-native'
import { Link } from 'expo-router'
export default function HomeScreen() {
return (
Welcome
{/* Link maps directly to file path */}
Open Settings
Edit Profile
)
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', padding: 24 },
title: { fontSize: 28, fontWeight: 'bold', marginBottom: 16 },
link: { fontSize: 16, color: '#61DAFB', marginTop: 12 },
})
```
The `Link` component handles navigation across all platforms. On the web, it renders an anchor tag with proper href attributes for SEO. On native platforms, it triggers stack-based navigation.
## Project Structure and Layout Files
Expo Router uses `_layout.tsx` files to define navigation containers. Each directory can have its own layout, creating nested navigation hierarchies. The root layout wraps the entire app, while nested layouts control specific sections.
A typical project structure looks like this:
```text
app/
_layout.tsx # Root layout (Stack or custom)
index.tsx # Home screen (/)
(tabs)/ # Tab group (parentheses = route group)
_layout.tsx # Tab navigator
home.tsx # /home tab
search.tsx # /search tab
profile.tsx # /profile tab
settings/
_layout.tsx # Settings stack layout
index.tsx # /settings
notifications.tsx # /settings/notifications
privacy.tsx # /settings/privacy
```
Route groups — directories wrapped in parentheses — organize files without affecting the URL. The `(tabs)` directory above creates a tab navigator, but the URLs remain `/home`, `/search`, and `/profile` rather than `/tabs/home`.
```typescript
// app/_layout.tsx
import { Stack } from 'expo-router'
export default function RootLayout() {
return (
)
}
```
The root layout also serves as the place to load fonts, initialize providers, and configure global settings — replacing the traditional `App.tsx` entry point.
## Building Tab Navigation with Expo Router
Tab navigation requires a `_layout.tsx` file inside a route group. Expo Router v6 introduces `NativeTabs` for platform-specific tab experiences, but the standard `Tabs` component from Expo Router covers most use cases.
```typescript
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router'
import { Ionicons } from '@expo/vector-icons'
export default function TabLayout() {
return (
(
),
}}
/>
(
),
}}
/>
(
),
}}
/>
)
}
```
Each tab screen file exports a standard React component. The tab bar icon, label, and badge are configured through the `options` prop in the layout.
## Dynamic Routes and Route Parameters
Dynamic segments use square brackets in the filename. A file named `[id].tsx` matches any single segment, while `[...slug].tsx` catches all remaining segments.
```typescript
// app/product/[id].tsx
import { View, Text, StyleSheet } from 'react-native'
import { useLocalSearchParams, Stack } from 'expo-router'
export default function ProductScreen() {
// Extract the dynamic parameter from the URL
const { id } = useLocalSearchParams<{ id: string }>()
return (
Product DetailsID: {id}
)
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24 },
heading: { fontSize: 24, fontWeight: 'bold', marginBottom: 8 },
id: { fontSize: 16, color: '#888' },
})
```
Navigating to `/product/42` renders this screen with `id` set to `"42"`. The `useLocalSearchParams` hook provides typed access to all route parameters.
For catch-all routes, `[...slug].tsx` captures entire path segments:
```typescript
// app/docs/[...slug].tsx
import { useLocalSearchParams } from 'expo-router'
export default function DocsScreen() {
// /docs/getting-started/installation → slug = ['getting-started', 'installation']
const { slug } = useLocalSearchParams<{ slug: string[] }>()
return
}
```
## Typed Routes for Compile-Time Safety
Expo Router generates route types automatically when `typed routes` is enabled. This catches broken links at compile time rather than at runtime.
Enable typed routes in `app.json`:
```json
{
"expo": {
"experiments": {
"typedRoutes": true
}
}
}
```
Once enabled, the `href` prop on `Link` and the argument to `router.push()` only accept valid route strings:
```typescript
// app/checkout.tsx
import { router } from 'expo-router'
function handleCheckout(cartId: string) {
// TypeScript validates this route exists
router.push(`/product/${cartId}`)
// This would cause a compile error if /nonexistent doesn't exist
// router.push('/nonexistent')
}
```
Typed routes pair well with `useLocalSearchParams`. The generated types ensure parameter names match between the route definition and the consuming component, preventing subtle bugs that surface only when navigating to a specific screen.
## Modal Screens and Presentation Options
Modals in Expo Router are regular screens configured with `presentation: 'modal'` in the layout. This approach keeps the file-based convention intact — a modal is just another route.
```typescript
// app/_layout.tsx
import { Stack } from 'expo-router'
export default function RootLayout() {
return (
{/* Modal screen slides up from the bottom */}
)
}
```
```typescript
// app/create-post.tsx
import { View, TextInput, Button, StyleSheet } from 'react-native'
import { router } from 'expo-router'
import { useState } from 'react'
export default function CreatePostModal() {
const [title, setTitle] = useState('')
const handleSubmit = () => {
// Submit logic here
router.back() // Dismiss the modal
}
return (
)
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24 },
input: {
borderWidth: 1,
borderColor: '#333',
borderRadius: 8,
padding: 12,
fontSize: 16,
marginBottom: 16,
},
})
```
Navigating to `/create-post` triggers the modal presentation. The `router.back()` call dismisses it, returning to the previous screen in the stack.
## Programmatic Navigation and the Router API
Beyond the `Link` component, Expo Router provides an imperative API through the `router` object. This handles navigation triggered by business logic rather than user taps.
```typescript
import { router } from 'expo-router'
// Push a new screen onto the stack
router.push('/profile/settings')
// Replace the current screen (no back button)
router.replace('/login')
// Go back to the previous screen
router.back()
// Navigate with parameters
router.push({
pathname: '/product/[id]',
params: { id: '42', source: 'recommendations' },
})
// Check if going back is possible
import { useRouter } from 'expo-router'
function BackButton() {
const router = useRouter()
return router.canGoBack() ? (