Expo Router Makes the File System Your Single Route Table
Expo Router turns files under app/ into routes and URLs, removing duplicate deep-link configuration. See how layouts and dynamic segments work across iOS, Android, and web.
27 Mar 2026, 01:02 UTC

The Problem: Two Sources of Truth for One Route
In a standard React Native project, adding a screen usually means updating a navigator file and then maintaining separate linking configuration for web and native deep links. When the route name in the navigator drifts from the path in the linking config, the app breaks in ways that are hard to spot until a user taps a stale URL.
Expo Router removes that duplication. Any file inside the app/ directory becomes a route, and the same path works across iOS, Android, and web without a separate linking registry. The file system is the API.
How the File Tree Maps to URLs
Under Expo Router, a file like app/profile/settings.tsx answers to /profile/settings. A file named _layout.tsx wraps the routes in its directory and typically renders a navigator such as Stack or Tabs from expo-router. Parenthesized directories like app/(tabs)/ group files without adding a segment to the URL, so app/(tabs)/index.tsx still maps to /.
This model assumes you are working in a project that already includes Expo Router—typically a default Expo template. If you are in a bare React Native project or relying on specific native modules, confirm that your setup supports the file-based router before restructuring directories.
Worked Example: Two Tabs and a Dynamic Route
Suppose you want a tabbed interface with a list and a detail view. In a default Expo project, create the following files.
First, app/(tabs)/_layout.tsx:
import { Tabs } from 'expo-router';
export default function TabLayout() {
return (
<Tabs>
<Tabs.Screen name="index" options={{ title: 'Home' }} /
<Tabs.Screen name="[id]" options={{ title: 'Detail' }} /
</Tabs>
);
}
Then app/(tabs)/index.tsx:
import { Link } from 'expo-router';
export default function HomeScreen() {
return (
<Link href="/42">Open item 42</Link>
);
}
And app/(tabs)/[id].tsx:
import { useLocalSearchParams } from 'expo-router';
export default function DetailScreen() {
const { id } = useLocalSearchParams();
return <Text>Item {id}</Text>;
}
Run the development server from the project root with npx expo start. Use the terminal shortcuts to launch iOS, Android, or web. Navigate to the detail tab and verify that the id param arrives through useLocalSearchParams. On web, open the local development URL for the same path and confirm the screen renders.
Risk: If you also have a separate React Navigation container mounted elsewhere, you may end up with duplicate navigators and a confusing back stack. Keep one navigation system per app.
Typed Routes: Useful, but Version-Sensitive
Expo Router can generate TypeScript types for routes so that router.push and Link href props catch invalid paths at build time. Treat these types as developer tooling, not a runtime guarantee. Typed routes have been opt-in or experimental across different Expo SDK releases, so verify the current behavior in your installed version before relying on them in CI.
To check, enable route type generation in your project config, then run npx tsc --noEmit from the project root. Expected check: hovering over a Link shows the generated route union. If the types do not appear, consult the Expo SDK documentation for your installed version rather than assuming an older snippet still applies.
What Becomes Harder When the Tree Is Implicit
Hiding the navigator tree inside the file system is convenient for standard stacks and tabs, but it can make complex modal stacks, deeply nested navigators, and custom screen transitions harder to express than in raw React Navigation. Layout behavior also differs between web and native in some cases, so a screen that renders correctly on iOS may need platform-specific adjustments.
Deep linking is another area where file names alone are not enough. Universal links require platform configuration such as associated domains on iOS or intent filters on Android. Creating app/profile/settings.tsx makes the route available inside the app, but you must still configure the native project and verify the link on a physical device.
Adoption Strategy
Expo Router is easiest to adopt in a new project. If you are migrating an existing React Navigation app, move incrementally: reconcile screen options, deep-link configuration, and any code that holds navigation refs before deleting the old navigator. After upgrading the Expo SDK in a branch, diff route resolution and layout rendering before merging.
The practical check is simple: open the same path on iOS, Android, and web, and confirm the params arrive without manual linking configuration. If that works, the file system is doing its job.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.