File-based screens and routes
A mobile app has screens instead of pages. Expo Router builds navigation from files: every file in the app folder becomes a screen with its own address, like pages in Next.js. You do not write a list of routes by hand.
A new project: the blog
The second app starts in this chapter, a blog reader. Create the project and clean up the template:
npx create-expo-app@latest blog
cd blog
npm run reset-project
npx expo startExpo Router is already installed and set up in the template.
A file is a route
| File | Screen address |
|---|---|
app/index.tsx | / |
app/about.tsx | /about |
app/posts/index.tsx | /posts |
app/posts/[id].tsx | /posts/1, /posts/2 and so on |
app/_layout.tsx | not a screen but a shared wrapper |
Create the "About" screen file:
// app/about.tsx
import { Text, View } from 'react-native';
export default function About() {
return (
<View style={{ flex: 1, padding: 16 }}>
<Text style={{ fontSize: 18 }}>A blog reader. Posts come from JSONPlaceholder.</Text>
</View>
);
}A screen must have a default export (export default); that is how Expo Router knows what to show.
Linking to a screen
Navigation is done with the Link component from expo-router. Open the main screen:
// app/index.tsx
import { Link } from 'expo-router';
import { Text, View } from 'react-native';
export default function Index() {
return (
<View style={{ flex: 1, padding: 16, gap: 12 }}>
<Text style={{ fontSize: 24 }}>Blog</Text>
<Link href="/about" style={{ color: '#2563eb', fontSize: 18 }}>
About
</Link>
</View>
);
}Tap the blue link and the "About" screen opens with about in the header and a back arrow. On Android you can also go back with the system back button. In a browser (the w key) the address changes to http://localhost:8081/about.
A link that looks like a button
By default Link renders as text. To turn your own component into a link, use asChild: Link passes the press to the nested Pressable:
// app/index.tsx
import { Link } from 'expo-router';
import { Pressable, Text } from 'react-native';
<Link href="/about" asChild>
<Pressable style={{ backgroundColor: '#2563eb', padding: 12, borderRadius: 8 }}>
<Text style={{ color: '#fff' }}>About</Text>
</Pressable>
</Link>A blue button appears that leads to the same screen.
Common mistake
Exporting the screen component by name: export function About() instead of export default function About(). Expo Router cannot find what to show, prints a warning about the missing default export in the terminal, and the screen does not open. In files of the app folder the screen is always exported with export default. And if href has a typo, you get an "Unmatched Route" screen instead.