GymApp uses Expo Router for navigation, which provides file-based routing similar to Next.js. Every file in the app/ directory automatically becomes a route.
How It Works
Expo Router automatically creates routes based on your file structure in the app/ directory.
The file structure determines the route structure:
app/
├── _layout.tsx → Root layout (not a route)
├── (tabs)/ → Route group (doesn't affect URL)
│ ├── _layout.tsx → Tab navigator layout
│ ├── index.tsx → / (home tab)
│ └── explore.tsx → /explore (explore tab)
└── modal.tsx → /modal
Route Groups
Folders wrapped in parentheses like (tabs) are route groups . They organize routes without affecting the URL structure.
Example from GymApp:
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router' ;
import { HapticTab } from '@/components/haptic-tab' ;
import { IconSymbol } from '@/components/ui/icon-symbol' ;
import { Colors } from '@/constants/theme' ;
import { useColorScheme } from '@/hooks/use-color-scheme' ;
export default function TabLayout () {
const colorScheme = useColorScheme ();
return (
< Tabs
screenOptions = { {
tabBarActiveTintColor: Colors [ colorScheme ?? 'light' ]. tint ,
headerShown: false ,
tabBarButton: HapticTab ,
} } >
< Tabs.Screen
name = "index"
options = { {
title: 'Home' ,
tabBarIcon : ({ color }) => < IconSymbol size = { 28 } name = "house.fill" color = { color } /> ,
} }
/>
< Tabs.Screen
name = "explore"
options = { {
title: 'Explore' ,
tabBarIcon : ({ color }) => < IconSymbol size = { 28 } name = "paperplane.fill" color = { color } /> ,
} }
/>
</ Tabs >
);
}
The (tabs) group creates a tab navigator without adding /tabs to the URL path.
Layouts
Files named _layout.tsx define layouts that wrap child routes.
Root Layout
// app/_layout.tsx
import { DarkTheme , DefaultTheme , ThemeProvider } from '@react-navigation/native' ;
import { Stack } from 'expo-router' ;
import { StatusBar } from 'expo-status-bar' ;
import { useColorScheme } from '@/hooks/use-color-scheme' ;
export const unstable_settings = {
anchor: '(tabs)' , // Sets initial route
};
export default function RootLayout () {
const colorScheme = useColorScheme ();
return (
< ThemeProvider value = { colorScheme === 'dark' ? DarkTheme : DefaultTheme } >
< Stack >
< Stack.Screen name = "(tabs)" options = { { headerShown: false } } />
< Stack.Screen name = "modal" options = { { presentation: 'modal' , title: 'Modal' } } />
</ Stack >
< StatusBar style = "auto" />
</ ThemeProvider >
);
}
Key Features of Root Layout
ThemeProvider : Wraps the entire app with theme context
Stack Navigator : Enables push/pop navigation between screens
Screen Options : Configure each screen’s presentation and header
Initial Route : unstable_settings.anchor sets the starting screen
Navigation
Navigate between routes using the Link component or navigation hooks.
Using Link
Using Router
Using Hooks
The declarative way to navigate: import { Link } from 'expo-router' ;
// Simple link
< Link href = "/modal" >
< ThemedText type = "link" > Open Modal </ ThemedText >
</ Link >
// Link with menu and actions
< Link href = "/modal" >
< Link.Trigger >
< ThemedText type = "subtitle" > Step 2: Explore </ ThemedText >
</ Link.Trigger >
< Link.Preview />
< Link.Menu >
< Link.MenuAction
title = "Action"
icon = "cube"
onPress = { () => alert ( 'Action pressed' ) }
/>
< Link.MenuAction
title = "Share"
icon = "square.and.arrow.up"
onPress = { () => alert ( 'Share pressed' ) }
/>
</ Link.Menu >
</ Link >
The imperative way to navigate: import { router } from 'expo-router' ;
// Navigate to a route
router . push ( '/modal' );
// Navigate back
router . back ();
// Replace current route
router . replace ( '/explore' );
// Navigate with params
router . push ({
pathname: '/user/[id]' ,
params: { id: '123' }
});
Access navigation from hooks: import { useRouter , usePathname } from 'expo-router' ;
function MyComponent () {
const router = useRouter ();
const pathname = usePathname ();
return (
< Button
title = "Go Back"
onPress = { () => router . back () }
/>
);
}
Screen Examples
Tab Screen (Home)
// app/(tabs)/index.tsx
import { Link } from 'expo-router' ;
import ParallaxScrollView from '@/components/parallax-scroll-view' ;
import { ThemedText } from '@/components/themed-text' ;
import { ThemedView } from '@/components/themed-view' ;
export default function HomeScreen () {
return (
< ParallaxScrollView
headerBackgroundColor = { { light: '#A1CEDC' , dark: '#1D3D47' } }
headerImage = { < Image source = { require ( '@/assets/images/partial-react-logo.png' ) } /> } >
< ThemedView >
< ThemedText type = "title" > Welcome! </ ThemedText >
</ ThemedView >
< ThemedView >
< Link href = "/modal" >
< ThemedText type = "link" > Open Modal </ ThemedText >
</ Link >
</ ThemedView >
</ ParallaxScrollView >
);
}
Modal Screen
// app/modal.tsx
import { Link } from 'expo-router' ;
import { StyleSheet } from 'react-native' ;
import { ThemedText } from '@/components/themed-text' ;
import { ThemedView } from '@/components/themed-view' ;
export default function ModalScreen () {
return (
< ThemedView style = { styles . container } >
< ThemedText type = "title" > This is a modal </ ThemedText >
< Link href = "/" dismissTo style = { styles . link } >
< ThemedText type = "link" > Go to home screen </ ThemedText >
</ Link >
</ ThemedView >
);
}
const styles = StyleSheet . create ({
container: {
flex: 1 ,
alignItems: 'center' ,
justifyContent: 'center' ,
padding: 20 ,
},
link: {
marginTop: 15 ,
paddingVertical: 15 ,
},
});
The dismissTo prop on Link dismisses the modal when navigating back to the specified route.
Route Parameters
Create dynamic routes using square brackets:
app/
└── user/
└── [id].tsx → /user/:id
Access parameters in your component:
import { useLocalSearchParams } from 'expo-router' ;
export default function UserScreen () {
const { id } = useLocalSearchParams ();
return < ThemedText > User ID: { id } </ ThemedText > ;
}
Navigation Options
Customize screen presentation in layouts:
< Stack >
{ /* Hide header */ }
< Stack.Screen name = "(tabs)" options = { { headerShown: false } } />
{ /* Modal presentation */ }
< Stack.Screen name = "modal" options = { { presentation: 'modal' , title: 'Modal' } } />
{ /* Custom header */ }
< Stack.Screen
name = "details"
options = { {
title: 'Details' ,
headerStyle: { backgroundColor: '#0a7ea4' },
headerTintColor: '#fff' ,
} }
/>
</ Stack >
Best Practices
Use Route Groups Organize related screens with () to keep URLs clean while maintaining structure.
Leverage Layouts Share navigation logic and UI across related screens with _layout.tsx files.
Type-Safe Navigation Use TypeScript with route params for compile-time safety.
Deep Linking File-based routing automatically supports deep linking out of the box.
Next Steps