expo-router
Use when implementing file-based routing in Expo with Expo Router. Covers app directory structure, navigation, layouts, dynamic routes, and deep linking.
What this skill does
# Expo Router
Use this skill when implementing file-based routing with Expo Router, the recommended navigation solution for Expo apps.
## Key Concepts
### File-Based Routing
Routes are defined by file structure:
```
app/
_layout.tsx # Root layout
index.tsx # / route
about.tsx # /about route
(tabs)/ # Group (not in URL)
_layout.tsx # Tabs layout
home.tsx # /home
profile.tsx # /profile
users/
[id].tsx # /users/:id dynamic route
index.tsx # /users route
```
### Basic Routes
```tsx
// app/index.tsx
import { View, Text } from 'react-native';
import { Link } from 'expo-router';
export default function Home() {
return (
<View>
<Text>Home Screen</Text>
<Link href="/about">Go to About</Link>
</View>
);
}
// app/about.tsx
export default function About() {
return (
<View>
<Text>About Screen</Text>
</View>
);
}
```
### Layouts
```tsx
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="index" options={{ title: 'Home' }} />
<Stack.Screen name="about" options={{ title: 'About' }} />
</Stack>
);
}
```
### Tab Navigation
```tsx
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
export default function TabLayout() {
return (
<Tabs>
<Tabs.Screen
name="home"
options={{
title: 'Home',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
}}
/>
</Tabs>
);
}
```
## Best Practices
### Dynamic Routes
```tsx
// app/users/[id].tsx
import { useLocalSearchParams } from 'expo-router';
import { View, Text } from 'react-native';
export default function UserDetails() {
const { id } = useLocalSearchParams<{ id: string }>();
return (
<View>
<Text>User ID: {id}</Text>
</View>
);
}
// Navigate to /users/123
<Link href="/users/123">View User</Link>
// Or
import { router } from 'expo-router';
router.push('/users/123');
```
### Programmatic Navigation
```tsx
import { router } from 'expo-router';
function MyComponent() {
const handlePress = () => {
// Navigate to route
router.push('/details');
// Navigate with params
router.push({
pathname: '/users/[id]',
params: { id: '123' },
});
// Replace current route
router.replace('/login');
// Go back
router.back();
};
return <Button title="Navigate" onPress={handlePress} />;
}
```
### Type-Safe Routes
```tsx
// types/navigation.ts
export type RootStackParamList = {
'/': undefined;
'/about': undefined;
'/users/[id]': { id: string };
'/posts/[id]': { id: string; title?: string };
};
// Usage with type safety
import { router } from 'expo-router';
import type { RootStackParamList } from './types/navigation';
// TypeScript will enforce correct params
router.push({
pathname: '/users/[id]' as const,
params: { id: '123' },
});
```
### Route Groups
Group routes without affecting URLs:
```
app/
(auth)/ # Group (not in URL)
login.tsx # /login
register.tsx # /register
_layout.tsx # Auth layout
(app)/ # Group (not in URL)
home.tsx # /home
profile.tsx # /profile
_layout.tsx # App layout
```
```tsx
// app/(auth)/_layout.tsx
import { Stack } from 'expo-router';
export default function AuthLayout() {
return (
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="login" />
<Stack.Screen name="register" />
</Stack>
);
}
```
## Common Patterns
### Authentication Flow
```tsx
// app/_layout.tsx
import { Slot, useRouter, useSegments } from 'expo-router';
import { useEffect } from 'react';
import { useAuth } from './hooks/useAuth';
export default function RootLayout() {
const { user, loading } = useAuth();
const segments = useSegments();
const router = useRouter();
useEffect(() => {
if (loading) return;
const inAuthGroup = segments[0] === '(auth)';
if (!user && !inAuthGroup) {
router.replace('/(auth)/login');
} else if (user && inAuthGroup) {
router.replace('/(app)/home');
}
}, [user, loading, segments]);
return <Slot />;
}
```
### Modal Routes
```tsx
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="modal"
options={{
presentation: 'modal',
title: 'Modal',
}}
/>
</Stack>
);
}
// app/modal.tsx
import { View, Text, Button } from 'react-native';
import { router } from 'expo-router';
export default function Modal() {
return (
<View>
<Text>Modal Content</Text>
<Button title="Close" onPress={() => router.back()} />
</View>
);
}
```
### Deep Linking
```json
// app.json
{
"expo": {
"scheme": "myapp",
"plugins": ["expo-router"]
}
}
```
```tsx
// Deep link: myapp://users/123
// Opens app/users/[id].tsx with id="123"
// Universal link: https://myapp.com/users/123
// Requires additional iOS/Android configuration
```
### Search Params
```tsx
import { useLocalSearchParams } from 'expo-router';
function ProductScreen() {
const { id, category, sort } = useLocalSearchParams<{
id: string;
category?: string;
sort?: string;
}>();
return (
<View>
<Text>Product: {id}</Text>
<Text>Category: {category}</Text>
<Text>Sort: {sort}</Text>
</View>
);
}
// Navigate with query params
<Link href="/products/123?category=electronics&sort=price">
View Product
</Link>
```
## Anti-Patterns
### Don't Use React Navigation Directly
```tsx
// Bad - Mixing Expo Router with React Navigation
import { NavigationContainer } from '@react-navigation/native';
// Good - Use Expo Router only
import { Stack } from 'expo-router';
```
### Don't Nest Navigators Incorrectly
```tsx
// Bad - Multiple Stack navigators without layout
// app/home.tsx
<Stack>
<Stack.Screen name="details" />
</Stack>
// Good - Use layouts for nested navigation
// app/home/_layout.tsx
<Stack>
<Stack.Screen name="index" />
<Stack.Screen name="details" />
</Stack>
```
### Don't Hardcode Routes
```tsx
// Bad - String literals everywhere
router.push('/users/123');
router.push('/prodcts/456'); // Typo won't be caught
// Good - Use constants or typed routes
const ROUTES = {
USER_DETAILS: (id: string) => `/users/${id}` as const,
PRODUCT_DETAILS: (id: string) => `/products/${id}` as const,
} as const;
router.push(ROUTES.USER_DETAILS('123'));
```
## Related Skills
- **expo-config**: Configuring deep linking
- **expo-modules**: Using navigation with Expo modules
Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.