BuildBot

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.

A mini file-path router (JS model)

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.

Loading editor…
Knowledge check

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.