Skip to main content
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>
  );
}
  • 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
Navigate between routes using the Link component or navigation hooks.

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>
  );
}
// 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>;
}
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

Build docs developers (and LLMs) love