Implementing Dynamic Routing and Shared Layouts with Expo Router
Learn how to replace manual navigation config with Expo Router's file-based system, utilizing dynamic segments and shared layouts for scalable React Native apps.
10 Dec 2025, 13:39 UTC

Solving the Navigation Boilerplate Problem
Traditional React Native navigation requires manual definition of stacks and screens in a central configuration file. As apps grow, this leads to "prop-drilling" navigation states and massive configuration files that are difficult to maintain. Expo Router solves this by using file-based routing, where the file system defines the navigation structure, similar to Next.js.
The primary takeaway is that by organizing your app/ directory correctly, you can automate the creation of navigation stacks and share UI elements (like headers or tab bars) across multiple screens without redefining them for every route.
Prerequisites
- Node.js installed (LTS version recommended).
- An Expo project initialized via
npx create-expo-app@latest. - Expo SDK version compatible with
expo-router(SDK 49 or newer). - Basic familiarity with React hooks and TypeScript.
Structuring the App Directory
Expo Router maps files in the app/ directory to routes. To implement a professional structure with shared layouts and dynamic data, follow this directory pattern:
app/
├── _layout.tsx # Root layout (Global providers, Root Stack)
├── index.tsx # Home route (/)
├── settings.tsx # Settings route (/settings)
└── user/
├── _layout.tsx # User section layout (Shared header/tabs)
└── [id].tsx # Dynamic user profile (/user/123)
Step 1: Define the Root Layout
The _layout.tsx file allows you to wrap sibling routes with a common UI or a navigation provider. Use the Stack component from expo-router to define the native navigation behavior.
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack
screenOptions={{
headerStyle: { backgroundColor: '#f4511e' },
headerTintColor: '#fff',
}}
>
<Stack.Screen name="index" options={{ title: 'Home' }} />
<Stack.Screen name="user" options={{ headerShown: false }} />
</Stack
);
}
Step 2: Implementing Dynamic Route Segments
To handle pages that depend on a variable (like a user ID), use brackets in the filename. For example, [id].tsx creates a dynamic segment that captures the value from the URL or navigation state.
// app/user/[id].tsx
import { Text, View } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
export default function UserProfile() {
const { id } = useLocalSearchParams();
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}
>
<Text>Viewing profile for User ID: {id}</Text>
</View
);
}
Step 3: Navigating Between Routes
You can navigate using the Link component for declarative navigation or the router object for imperative logic (e.g., after a form submission).
// app/index.tsx
import { Link, router } from 'expo-router';
import { View, Button } from 'react-native';
export default function Home() {
return (
<View style={{ gap: 10, padding: 20 }}
>
<Link href="/settings" style={{ color: 'blue' }}
>
Go to Settings
</Link>
<Button
title="Go to User 123"
onPress={() => router.push('/user/123')}
/>
</View
);
}
Verification and Diagnostics
To verify the routing is functioning correctly, run the following command in your terminal:
npx expo start
Checklist for success:
- Root Route: Opening the app should immediately render
app/index.tsx. - Dynamic Parsing: Navigating to
/user/123should display "Viewing profile for User ID: 123". - Layout Persistence: Navigating between screens within the
user/folder should maintain the UI defined inapp/user/_layout.tsxwithout re-mounting the entire screen.
Limitations and Risks
| Risk | Impact | Mitigation |
|---|---|---|
| Renaming files | Breaks existing deep links and Link references. |
Search and replace all href strings when renaming route files. |
| Web Divergence | Production web builds may behave differently than Expo Go. | Run npx expo export --platform web to test static routing. |
| Complex Nesting | Too many _layout.tsx files can lead to confusing UI nesting. |
Keep directory depth to 3 levels maximum for maintainability. |
Rollback Procedure
If the file-based routing causes conflicts with an existing navigation library (like @react-navigation/native), you can revert by:
- Deleting the
app/directory. - Moving your screens back to a
src/screens/directory. - Removing
expo-routerfrompackage.jsonand runningnpm install. - Restoring your manual
NavigationContainerandStack.Navigatorconfiguration inApp.js.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.