> expo-router
Build native mobile apps with file-based routing using Expo Router — React Native navigation that works like Next.js. Use when someone asks to "add navigation to React Native", "Expo Router", "file-based routing for mobile", "deep linking in React Native", "mobile app navigation", or "React Native routing like Next.js". Covers file-based routes, layouts, deep linking, tabs, stacks, and universal links.
curl "https://skillshub.wtf/TerminalSkills/skills/expo-router?format=md"Expo Router
Overview
Expo Router brings file-based routing to React Native — the same pattern as Next.js but for mobile apps. Drop a file in app/, get a screen. Folder structure defines navigation hierarchy: stacks, tabs, drawers, and modals. Deep linking works automatically — every route has a URL. Build iOS, Android, and web from one codebase with the same routing system.
When to Use
- Building a React Native / Expo app that needs navigation
- Want file-based routing instead of manual React Navigation config
- Need deep linking and universal links without extra setup
- Building a universal app (iOS + Android + Web)
- Migrating from React Navigation to a simpler routing model
Instructions
Setup
npx create-expo-app@latest my-app --template tabs
cd my-app
npx expo start
File-Based Routes
app/
├── _layout.tsx # Root layout (wraps all screens)
├── index.tsx # / (home screen)
├── about.tsx # /about
├── (tabs)/ # Tab navigation group
│ ├── _layout.tsx # Tab bar configuration
│ ├── index.tsx # First tab (home)
│ ├── explore.tsx # Second tab
│ └── profile.tsx # Third tab
├── settings/
│ ├── _layout.tsx # Stack layout for settings
│ ├── index.tsx # /settings
│ ├── account.tsx # /settings/account
│ └── notifications.tsx # /settings/notifications
├── [id].tsx # /123 (dynamic route)
├── post/
│ └── [slug].tsx # /post/my-first-post
└── +not-found.tsx # 404 screen
Root Layout
// app/_layout.tsx — Root layout with stack navigation
import { Stack } from "expo-router";
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="settings" options={{ title: "Settings" }} />
<Stack.Screen
name="modal"
options={{ presentation: "modal", title: "Info" }}
/>
</Stack>
);
}
Tab Navigation
// app/(tabs)/_layout.tsx — Bottom tab bar
import { Tabs } from "expo-router";
import { Ionicons } from "@expo/vector-icons";
export default function TabLayout() {
return (
<Tabs screenOptions={{ tabBarActiveTintColor: "#007AFF" }}>
<Tabs.Screen
name="index"
options={{
title: "Home",
tabBarIcon: ({ color }) => <Ionicons name="home" size={24} color={color} />,
}}
/>
<Tabs.Screen
name="explore"
options={{
title: "Explore",
tabBarIcon: ({ color }) => <Ionicons name="compass" size={24} color={color} />,
}}
/>
<Tabs.Screen
name="profile"
options={{
title: "Profile",
tabBarIcon: ({ color }) => <Ionicons name="person" size={24} color={color} />,
}}
/>
</Tabs>
);
}
Navigation and Dynamic Routes
// app/(tabs)/index.tsx — Home screen with navigation
import { Link, router } from "expo-router";
import { View, Text, Pressable, FlatList } from "react-native";
export default function HomeScreen() {
const posts = usePosts();
return (
<View>
{/* Declarative navigation */}
<Link href="/about">About</Link>
<Link href="/settings">Settings</Link>
<Link href="/post/hello-world">My Post</Link>
{/* Programmatic navigation */}
<Pressable onPress={() => router.push("/settings/account")}>
<Text>Go to Account</Text>
</Pressable>
{/* Dynamic routes */}
<FlatList
data={posts}
renderItem={({ item }) => (
<Link href={`/post/${item.slug}`}>
<Text>{item.title}</Text>
</Link>
)}
/>
</View>
);
}
// app/post/[slug].tsx — Dynamic route screen
import { useLocalSearchParams } from "expo-router";
export default function PostScreen() {
const { slug } = useLocalSearchParams<{ slug: string }>();
const post = usePost(slug);
return (
<View>
<Text style={{ fontSize: 24 }}>{post?.title}</Text>
<Text>{post?.content}</Text>
</View>
);
}
Deep Linking (Automatic)
// app.json — Deep linking just works
{
"expo": {
"scheme": "myapp",
"web": { "bundler": "metro" }
}
}
Every route automatically gets a URL:
myapp://→ Home screenmyapp://post/hello-world→ Post screenhttps://myapp.com/post/hello-world→ Same screen (universal links)
Examples
Example 1: Build a social media app navigation
User prompt: "Set up navigation for a social app — tabs for feed/search/profile, stack for post details, and modal for creating posts."
The agent will create tab layout, nested stack routes, and a modal route with proper animations and deep linking.
Example 2: Add authentication flow
User prompt: "Add login/signup screens that show before the main app tabs."
The agent will create a route group for auth screens, use layout redirection based on auth state, and protect tab routes.
Guidelines
- File = route —
app/about.tsx=/aboutscreen _layout.tsxfor navigation containers — Stack, Tabs, Drawer(group)for route groups — organize without affecting URL[param]for dynamic routes — access viauseLocalSearchParams- Deep linking is automatic — every route has a URL
Linkfor declarative,routerfor programmatic — navigation+not-found.tsxfor 404 — catches unmatched routes- Typed routes —
hrefautocompletes with TypeScript - Web support — same routes work on web with
expo-router presentation: "modal"in options — for modal screens
> related_skills --same-repo
> zustand
You are an expert in Zustand, the small, fast, and scalable state management library for React. You help developers manage global state without boilerplate using Zustand's hook-based stores, selectors for performance, middleware (persist, devtools, immer), computed values, and async actions — replacing Redux complexity with a simple, un-opinionated API in under 1KB.
> zod
You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.
> xero-accounting
Integrate with the Xero accounting API to sync invoices, expenses, bank transactions, and contacts — and generate financial reports like P&L and balance sheet. Use when: connecting apps to Xero, automating bookkeeping workflows, syncing accounting data, or pulling financial reports programmatically.
> windsurf-rules
Configure Windsurf AI coding assistant with .windsurfrules and workspace rules. Use when: customizing Windsurf for a project, setting AI coding standards, creating team-shared Windsurf configurations, or tuning Cascade AI behavior.