React, Off the Web
File-based navigation with expo-router
Lesson 2 of 5
What you'll learn
- Map files in app/ to screens, exactly like Next.js routes map to pages
- Compose Stack and Tabs navigators with _layout files and route groups
- Navigate with Link and read dynamic segments via useLocalSearchParams
Native apps have no URL bar, but expo-router gives you file-based routing anyway: every file in app/ becomes a screen, and the directory structure becomes the navigation tree. If you've used the Next.js App Router, this is the same idea with native transitions instead of page loads.
app/
_layout.tsx → root navigator (a Stack)
index.tsx → "/"
settings.tsx → "/settings"
users/
[id].tsx → "/users/42" (dynamic segment)
A _layout.tsx file wraps every route in its directory with a navigator. The two you'll use constantly are Stack (screens slide on top of each other, back gesture included) and Tabs (a bottom tab bar):
// app/_layout.tsx — every screen renders inside this Stack
import { Stack } from "expo-router";
export default function RootLayout() {
return <Stack screenOptions={{ headerShown: true }} />;
}
Navigation is declarative with <Link>, or imperative with the router object. Dynamic segments arrive through a hook:
// app/index.tsx
import { Link } from "expo-router";
import { Text } from "react-native";
export default function Home() {
return <Link href="/users/42"><Text>Open user 42</Text></Link>;
}
// app/users/[id].tsx
import { useLocalSearchParams } from "expo-router";
import { Text } from "react-native";
export default function User() {
const { id } = useLocalSearchParams<{ id: string }>();
return <Text>User {id}</Text>;
}
Route groups
Parenthesized directories like (tabs) or (auth) group routes without adding a URL segment. The classic pattern: the root Stack contains a (tabs) group holding the tab navigator, plus modal screens that push over the tabs. (auth) holds login screens you conditionally redirect into. app/(tabs)/index.tsx is still just /.
Deep links for free
Because every screen has a path, expo-router gives you deep linking with zero extra config — myapp://users/42 opens the right screen with id populated. Web builds of the same codebase get real URLs.
The challenge is a JS model of the router's core trick: matching a path against file-derived route patterns, extracting [bracket] params, and ignoring group segments.
Click the links: the matcher turns app/ file paths into patterns, skips (group) segments, and extracts [id] like useLocalSearchParams. Try adding a users/[id]/posts/[postId].tsx route.
What does a parenthesized directory like (tabs) do in expo-router?
Next: past the JS sandbox — cameras, notifications, and the permissions dance that guards them.
Saved on this device. Sign in to sync your progress everywhere.