Expo Router represents a paradigm shift in how developers build navigation for React Native applications. Unlike traditional navigation solutions that require manual route configuration, Expo Router leverages file-based routing where your project structure directly translates into your app's navigation hierarchy. This approach, inspired by frameworks like Next.js, brings the familiar web development experience to native mobile development while maintaining full native performance through its foundation on React Navigation.
The router automatically generates routes based on your app directory structure, meaning each file becomes a screen and every directory segment defines a URL path. This declarative approach eliminates the boilerplate configuration traditionally associated with mobile navigation, allowing developers to focus on building features rather than managing routing state. With support for dynamic segments, nested layouts, deep linking, and type-safe navigation, Expo Router has become the recommended solution for navigation in Expo and React Native applications.
For teams building cross-platform mobile applications, understanding file-based routing patterns is essential. Our /services/web-development/ expertise helps organizations implement modern navigation architectures that scale across iOS, Android, and web platforms.
Everything you need for production-ready navigation
File-Based Routing
Your project structure directly defines navigation--no manual configuration required.
Type-Safe Navigation
TypeScript integration catches routing errors at build time for reliable deployments.
Deep Linking
Native support for URL-based navigation from notifications, emails, and external links.
Offline-First
Complete navigation functionality without network connectivity.
Nested Layouts
Complex navigation hierarchies with shared UI elements like tabs and drawers.
Universal Support
Same code works across iOS, Android, and web platforms.
The Ground Rules of File-Based Routing
Understanding Expo Router requires internalizing its fundamental principles. These rules govern how the router interprets your project structure and generates navigation.
Rule 1: All Screens Are Files in the App Directory
Every navigation route in your application is defined by files and subdirectories inside the app directory. Each file exports a default React component that represents a distinct page in your app. This creates an intuitive mapping between your codebase and your application's screen flow.
Rule 2: Every Page Has a URL
Unlike traditional navigation libraries where screens exist only within the app's runtime state, Expo Router gives every screen a URL. This URL can be shared, deep-linked to, and bookmarked just like web pages.
Rule 3: Index Files Define Route Entry Points
The index.tsx file in any directory serves as that directory's entry point. The root app/index.tsx is your application's initial route--the first screen users see when launching the app.
Rule 4: Dynamic Segments Use Square Brackets
When you need routes that accept variable parameters--such as product IDs, user identifiers, or any dynamic content--use square bracket notation. A file named [id].tsx creates a dynamic segment that captures values from the URL.
File Structure Example
app/
├── _layout.tsx
├── index.tsx
├── settings.tsx
├── profile/
│ ├── index.tsx
│ └── edit.tsx
└── products/
├── index.tsx
└── [id].tsx
This structure automatically generates routes for /, /settings, /profile, /profile/edit, /products, and /products/[dynamic-id].
Getting Started with Expo Router
Installation of Expo Router begins with adding the package to your Expo project. The router integrates seamlessly with existing Expo workflows.
npx expo install expo-router
After installation, modify your app entry point to use the router's provider:
import { registerRootComponent } from 'expo';
import { ExpoRoot } from 'expo-router';
export default function App() {
return <ExpoRoot />;
}
registerRootComponent(App);
This minimal setup enables the complete file-based routing system. From here, your app directory structure defines all navigation.
Navigation Basics: Linking Screens Together
Expo Router provides multiple approaches for navigating between screens, each suited to different scenarios and coding styles.
Using the Link Component
The <Link> component provides declarative navigation similar to HTML anchor tags:
import { Link } from 'expo-router';
export default function HomeScreen() {
return (
<View>
<Link href="/products/42">View Featured Product</Link>
<Link href="/settings" asChild>
<Button title="Go to Settings" />
</Link>
</View>
);
}
Programmatic Navigation with useRouter
For imperative navigation, the useRouter hook provides programmatic control:
import { useRouter } from 'expo-router';
export default function CheckoutButton({ cartItems }) {
const router = useRouter();
const handleCheckout = async () => {
const order = await processOrder(cartItems);
router.push({
pathname: '/order-confirmation',
params: { orderId: order.id }
});
};
return <Button onPress={handleCheckout} title="Complete Purchase" />;
}
Passing Parameters Between Screens
Parameters pass through the URL query string:
router.push('/profile/user123?tab=activity');
On the destination screen, retrieve parameters with useLocalSearchParams:
import { useLocalSearchParams } from 'expo-router';
export default function ProfileScreen() {
const { userId, tab } = useLocalSearchParams();
// URL /profile/user123?tab=activity makes userId="user123", tab="activity"
}
Layouts and Nested Navigation
Layout files provide shared navigation infrastructure across multiple screens. A _layout.tsx file defines UI elements that persist across route changes.
Creating a Stack Layout
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack
screenOptions={{
headerStyle: { backgroundColor: '#f4511e' },
headerTintColor: '#fff',
}}
>
<Stack.Screen name="index" options={{ title: 'Home' }} />
<Stack.Screen name="settings" options={{ title: 'Settings' }} />
</Stack>
);
}
Tab-Based Layouts
// app/_layout.tsx
import { Tabs } from 'expo-router';
export default function Layout() {
return (
<Tabs>
<Tabs.Screen name="index" options={{ title: 'Home' }} />
<Tabs.Screen name="profile" options={{ title: 'Profile' }} />
</Tabs>
);
}
Nested Routes and Layout Groups
Route groups--directories wrapped in parentheses--create nested layouts without adding segments to the URL path:
app/
├── _layout.tsx
├── (authenticated)/
│ ├── _layout.tsx
│ ├── dashboard.tsx
│ └── settings.tsx
└── (public)/
├── _layout.tsx
├── login.tsx
└── register.tsx
Dynamic Routes for Flexible Navigation
Dynamic routes handle variable content through URL patterns.
Basic Dynamic Segments
// app/products/[productId].tsx
import { useLocalSearchParams } from 'expo-router';
export default function ProductDetail() {
const { productId } = useLocalSearchParams();
// URL /products/abc123 makes productId = "abc123"
}
Multiple Dynamic Segments
// app/stores/[city]/[storeId].tsx
export default function StoreDetail() {
const { city, storeId } = useLocalSearchParams();
// /stores/toronto/store456 → city="toronto", storeId="store456"
}
Catch-All Segments
// app/docs/[...slug].tsx
export default function DocPage() {
const { slug } = useLocalSearchParams();
// slug is an array of path segments
// /docs/getting-started → slug = ["getting-started"]
}
Deep Linking Configuration
Deep linking enables external URLs to open specific screens within your application.
Configuring Deep Link Schemes
Add deep link schemes to your app.json:
{
"expo": {
"schemes": ["myapp", "myapp-dev"]
}
}
Universal Linking for Web Compatibility
{
"expo": {
"ios": {
"associatedDomains": ["applinks:yourapp.com"]
},
"android": {
"intentFilters": [
{
"action": "VIEW",
"data": {
"scheme": "https",
"host": "*.yourapp.com"
}
}
]
}
}
}
Testing Deep Links
# iOS Simulator
xcrun simctl openurl booted "myapp://products/test"
# Android
adb shell am start -W -a android.intent.action.VIEW -d "myapp://products/test"
Best Practices for Production Applications
Error Handling with Error Boundaries
// app/+error.tsx
import { ErrorBoundaryProps } from 'expo-router';
export default function ErrorBoundary({ error, retry }: ErrorBoundaryProps) {
return (
<View style={styles.errorContainer}>
<Text>Something went wrong</Text>
<Text>{error?.message}</Text>
<Button onPress={retry} title="Try Again" />
</View>
);
}
Performance Optimization
- Lazy load route components using React.lazy when appropriate
- Memoize expensive navigation-dependent computations
- Use proper key props in lists that depend on navigation parameters
- Coordinate deep link handling with state restoration
To optimize navigation performance, consider implementing lazy loading patterns similar to those used in web development. See our guide on lazy loading components in React for detailed techniques.
Type Safety with TypeScript
function navigateToProduct(id: string) {
router.push({ pathname: '/products/[id]', params: { id } });
}
The router's TypeScript integration catches navigation errors at build time. For a deeper understanding of TypeScript's type system and when to use advanced types like never and unknown, explore our guide on when to use never and unknown types in TypeScript.
Frequently Asked Questions
How does Expo Router compare to React Navigation?
Expo Router is built on top of React Navigation, so it uses the same native APIs for transitions. The key difference is that Expo Router uses file-based routing instead of manual configuration, reducing boilerplate while maintaining full native performance.
Can I use Expo Router with existing React Navigation code?
Yes, Expo Router is compatible with React Navigation libraries. You can gradually migrate existing navigation to Expo Router or use them together in the same project.
Does Expo Router work with bare React Native projects?
While designed primarily for Expo projects, Expo Router can be configured for bare React Native projects. However, the setup requires additional configuration compared to managed Expo projects.
How do I handle authentication flows with Expo Router?
Use layout groups to create separate navigation trees for authenticated and unauthenticated users. Route groups in parentheses create nested layouts without affecting the URL structure.
Conclusion
Expo Router transforms React Native navigation from configuration-heavy setup to intuitive file-based architecture. By aligning mobile development patterns with web conventions, it reduces cognitive overhead while maintaining native performance. The router's feature set--dynamic routes, nested layouts, deep linking, and type safety--addresses the full spectrum of navigation requirements for modern applications.
Whether you're building a simple utility app or a complex platform, Expo Router provides the foundation for navigable, accessible, and maintainable user experiences. Its continued development and strong community support make it the recommended choice for navigation in Expo and React Native projects.
For teams looking to build comprehensive cross-platform applications, pairing Expo Router with a well-structured TypeScript implementation creates a powerful foundation. Our TypeScript development expertise helps organizations deliver maintainable, type-safe mobile applications that scale.