← All shorts
38Expo SDK 50+2 min read

Expo Router: File-Based Navigation

Expo Router turns your file tree into your navigation graph, replacing manually wired React Navigation stacks.

Wiring a React Navigation stack means maintaining a param-list type, a navigator tree, and a screen registry by hand, all of which have to stay in sync as screens get added or renamed.

#The old way

import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen';
import ProfileScreen from './screens/ProfileScreen';

export type RootStackParamList = {
  Home: undefined;
  Profile: { userId: string };
};

const Stack = createNativeStackNavigator<RootStackParamList>();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Profile" component={ProfileScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

#The new way

The file app/_layout.tsx defines the root stack, and each screen's route is just its path under app/:

import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="index" options={{ title: 'Home' }} />
      <Stack.Screen name="profile/[userId]" options={{ title: 'Profile' }} />
    </Stack>
  );
}

app/profile/[userId].tsx becomes the profile route automatically, with the dynamic segment read via useLocalSearchParams:

import { View, Text } from 'react-native';
import { useLocalSearchParams } from 'expo-router';

export default function ProfileScreen() {
  const { userId } = useLocalSearchParams<{ userId: string }>();

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Profile for {userId}</Text>
    </View>
  );
}
import { Link } from 'expo-router';

export function ProfileLink({ userId }: { userId: string }) {
  return <Link href={`/profile/${userId}`}>View profile</Link>;
}

#Why it matters

  • The file tree is the single source of truth for the nav graph — no separate param-list type to keep in sync by hand.
  • Deep links work automatically, since every screen file is already a URL-shaped route.
  • Layouts nest naturally through _layout.tsx files instead of hand-nested navigator boilerplate.

#Gotcha

A file under app/ only becomes a route if it has a default export. A stray non-default export, a shared component you accidentally dropped inside app/, or a typo'd filename either fails to register as a route or, worse, silently registers as an unintended one — keep non-route files outside app/ (in components/, for example).

react-nativeexponavigation

Related shorts