Why I Switched to Expo Router: Best Practices and Implementation Guide
After years of using React Navigation for my side projects, I decided to give Expo Router another try. My first experience with expo…

After years of using React Navigation for my side projects, I decided to give Expo Router another try. My first experience with expo router(version 1) was less than ideal. At the time, I couldn’t understand the hype around it. I even thought, “Who wants file-based routing for a mobile app?”
Turns out, I do!
In this article, I’ll walk you through how to implement Expo Router, share best practices I’ve discovered, and show you why it’s worth adopting.
Setting Up Expo Router
Step 1: Install Dependencies
Run the following command to install Expo Router and related packages:
npx expo install expo-router react-native-safe-area-context react-native-screens expo-linking expo-constants expo-status-bar
Step 2: Configure Your Entry Point
Add the following to your package.json
to define the entry point for Expo Router:
{
"main": "expo-router/entry"
}
Step 3: Add Deep Linking
In your app configuration (e.g., app.json
or app.config.js
), specify a custom deep linking scheme:
{
"scheme": "your-app-scheme"
}
Step 4: Update Babel Configuration
Modify your babel.config.js
file:
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
};
};
With the configuration complete, let’s dive into an example app.
Building a Tab-Based Navigation
File Structure
Here’s the file structure for a simple app with Two tabs:
app
_layout.tsx
(tabs)
_layout.tsx
index.tsx
settings.tsx
Implementing the Layouts
Root Layout (app/_layout.tsx
)
import { Stack } from 'expo-router/stack';
export default function Layout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
);
}
Tab Layout (app/(tabs)/_layout.tsx
)
import FontAwesome from '@expo/vector-icons/FontAwesome';
import { Tabs } from 'expo-router';
export default function TabLayout() {
return (
<Tabs screenOptions={{ tabBarActiveTintColor: 'blue' }}>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color }) => <FontAwesome size={28} name="home" color={color} />,
}}
/>
<Tabs.Screen
name="settings"
options={{
title: 'Settings',
tabBarIcon: ({ color }) => <FontAwesome size={28} name="cog" color={color} />,
}}
/>
</Tabs>
);
}
Example Tab Screens
For app/(tabs)/index.tsx
and app/(tabs)/settings.tsx
:
import { View, Text, StyleSheet } from 'react-native';
export default function Tab() {
return (
<View style={styles.container}>
<Text>Tab [Home|Settings]</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
Structuring Your Project
To avoid deeply nested navigators, follow these best practices inspired by React Navigation’s documentation:
- Reduce Nesting: Deep nesting can cause performance issues on low-end devices and make your code harder to manage.
- Meaningful Routes: Use file-based routing to create logical and predictable paths. For example, instead of nesting stacks within stacks, create folder-based routes like
settings/account
.
Example File Structure
app
_layout.tsx
settings/
account.tsx
home/
notifications.tsx
(tabs)/
_layout.tsx
index.tsx
settings.tsx
Root Layout (app/_layout.tsx
)
import { Stack } from 'expo-router/stack';
export default function Layout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="settings/account" options={{ headerShown: false }} />
<Stack.Screen name="home/notifications" options={{ headerShown: false }} />
</Stack>
);
}
This structure ensures clean, meaningful routes like /settings/account
instead of overly nested setups.
Passing Parameters
To pass parameters to screens, use bracket notation in your filenames.
Example 1: Passing a Single Parameter
File: app/[id].tsx
import { useLocalSearchParams } from 'expo-router';
import { View, Text } from 'react-native';
export default function Route() {
const { id } = useLocalSearchParams<{ id: string }>();
return (
<View>
<Text>ID: {id}</Text>
</View>
);
}
Example 2: Parsing Arrays
Navigate to screen with array param:
router.push({
pathname: "/settings/account",
params: {
ids: JSON.stringify([1, 2, 3]),
},
})
import { useLocalSearchParams } from 'expo-router';
import { View, Text } from 'react-native';
export default function Route() {
const { ids } = useLocalSearchParams<{ ids: string }>();
const parsedIds = JSON.parse(ids);
return (
<View>
<Text>IDs: {parsedIds.join(', ')}</Text>
</View>
);
}
Why Expo Router?
Expo Router simplifies navigation by combining React Native’s flexibility with the familiarity of file-based routing. Its structure makes your project easier to maintain, navigate, and scale.
By following the practices outlined above, you’ll enjoy a more organized codebase and a smoother development experience.
Try BanKan Board — The Project Management App Made for Developers, by Developers
If you’re tired of complicated project management tools with story points, sprints, and endless processes, BanKan Board is here to simplify your workflow. Built with developers in mind, BanKan Board lets you manage your projects without the clutter.
Key Features:
- No complicated processes: Focus on what matters without the overhead of traditional project management systems.
- Claude AI Assistant: Get smart assistance to streamline your tasks and improve productivity.
- Free to Use: Start using it without any upfront cost.
- Premium Features: Upgrade to unlock advanced functionality tailored to your team’s needs.
Whether you’re building a side project, managing a team, or collaborating on open-source software, BanKan Board is designed to make your life easier. Try it today!