Choosing Expo Router for File‑Based Navigation in React Native
Learn how Expo Router simplifies navigation with file‑based routes, type‑safe links, and built‑in deep linking, plus the trade‑offs to consider before adopting.
08 Dec 2025, 13:09 UTC

Problem: Keeping navigation in sync as the app grows
As a React Native project adds screens, manually registering routes in a NavigationContainer becomes tedious and error‑prone. A typo in a route name or a missed deep‑link configuration often surfaces only at runtime, leading to broken navigation flows.
Thesis: Expo Router gives you file‑system routes, type‑safe links, and built‑in deep linking without manual navigator setup
By treating the app/ (or src/app/) directory as the source of truth, Expo Router turns each file into a route and each layout.tsx into a navigator wrapper. This eliminates imperative registration, provides compile‑time href validation, and makes the same href work as a universal URL on iOS, Android, and the web.
How Expo Router works
Expo Router scans the app/ folder. A file like app/profile/[id].tsx becomes a route with a dynamic parameter id. A sibling layout.tsx file wraps its sibling files in a navigator (stack, tabs, or drawer). Route groups surrounded by parentheses—for example (auth)—are invisible in the URL but let you nest layouts for auth‑guarded sections.
Worked example: Auth‑guarded tabs
- Create the folder structure
app/ ├── (auth)/ │ ├── layout.tsx │ └── login.tsx ├── (app)/ │ ├── layout.tsx │ ├── index.tsx │ └── profile/[id].tsx └── _layout.tsx // optional root layout - Define layouts
// app/(auth)/layout.tsx import { Stack } from 'expo-router'; export default function AuthLayout() { return {/* children: login, signup */}; } // app/(app)/layout.tsx import { Tabs } from 'expo-router'; export default function AppLayout() { return ( ); } - Type‑safe navigation
// anywhere in the app import { Link, useRouter } from 'expo-router'; function Home() { const router = useRouter(); return ( <> View profile router.push('/(auth)/login')}>Log in ); } - Deep link test
# Run the dev server npx expo start --dev-client # On a device or emulator, open the URL npx uri-scheme open 'exp://localhost:8081/--/(app)/profile/123' --iosIf the link opens the profile screen with ID 123, the routing and deep‑link configuration are working.
Trade‑offs and limitations
- SDK version: requires Expo SDK 49+ and React Native 0.72+. Older projects must upgrade first, which may involve resolving peer‑dependency conflicts.
- Layout files cannot contain dynamic segments; you cannot have
app/[tenant]/layout.tsx. Dynamic params are only allowed in leaf route files, so per‑tenant layout customization needs a wrapper component or context. - Type‑safe hrefs depend on the
expo-router/babel-plugin. If the plugin is missing or mis‑configured, TypeScript falls back to plain strings and you lose compile‑time checking. - Static web output (
output: 'static') produces SEO‑friendly HTML but does not support API routes or server‑side data fetching; you would need a separate Next.js/Remix backend for SSR.
Actionable steps
- Upgrade to Expo SDK 49 (or later) if you aren’t already:
npx expo upgrade. - Add the router template:
npx create-expo-app my-app --template routeror migrate by addingapp/and the babel plugin. - Set up your root
layout.tsx(optional) and decide where to place route groups for auth vs. public screens. - Replace manual
NavigationContainerusage withExpo.Routerwhere possible; you can keep a legacy navigator and render<Slots />inside it for incremental migration. - Verify type safety by deliberately breaking a
Linkhref and confirming the TypeScript error appears. - Test a deep link with
uri-schemeas shown; on production configure universal links (iOS) and intent filters (Android) viaapp.json.
By treating the file system as the source of truth for navigation, Expo Router reduces boilerplate, catches broken links early, and gives you a single source for web and native deep links. The main cost is the SDK upgrade and the inability to put dynamic segments in layout files, but for most apps the trade‑off yields faster development and fewer runtime navigation bugs.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.