React Navigation był przez lata jedynym sensownym wyborem do nawigacji w React Native. Działa, jest elastyczny, ma ogromną społeczność. Ma też jeden fundamentalny problem: każdą trasę musisz zdefiniować ręcznie w konfiguracji. Przy 20+ ekranach to jest boilerplate, który boli.
Expo Router 3 rozwiązuje ten problem radykalnie — plikami. Tworzysz plik `app/profile/[id].tsx`, automatycznie masz trasę `/profile/:id`. Znajome jeśli pracowałeś z Next.js. I to jest właśnie punkt — ta sama mentalność, ten sam Developer Experience, tylko na mobile.
Expo Router 3 vs React Navigation: czym się różnią
React Navigation to **konfiguracyjny** model nawigacji. Definiujesz navigator, rejestrujesz w nim screeny, przekazujesz props. Masz pełną kontrolę, ale za cenę ceremonii.
```typescript // React Navigation — klasyczna konfiguracja const Stack = createStackNavigator()
function AppNavigator() {
return (
Expo Router to **file-based** model. Struktura katalogów = struktura nawigacji. Zero konfiguracji dla podstawowych przypadków.
``` app/ index.tsx → / profile/ [id].tsx → /profile/:id (tabs)/ _layout.tsx → definicja tab baru home.tsx → /home (tab) settings.tsx → /settings (tab) ```
Kluczowe różnice: - **Expo Router** — mniej boilerplate, URL-based nawigacja, wbudowany deep linking, lepsza integracja z web (Expo Web) - **React Navigation** — więcej kontroli, dojrzalszy ekosystem, łatwiej w legacy projektach, więcej opcji animacji
Setup: nowy projekt z TypeScript
```bash npx create-expo-app@latest my-app --template # Wybierz: "Navigation (TypeScript)" cd my-app npx expo start ```
Template tworzy gotową strukturę z Expo Router, TypeScript i podstawowym layoutem tab-based. Nic więcej nie potrzebujesz żeby zacząć.
Podstawowa struktura plików
``` app/ _layout.tsx ← root layout (provider'y, fonts) index.tsx ← ekran główny / (tabs)/ _layout.tsx ← konfiguracja tab navigatora index.tsx ← pierwsza zakładka explore.tsx ← druga zakładka profile/ [id].tsx ← dynamiczny segment +not-found.tsx ← 404 ```
**Root layout** (`app/_layout.tsx`):
```typescript import { Stack } from 'expo-router' import { useFonts } from 'expo-font' import * as SplashScreen from 'expo-splash-screen' import { useEffect } from 'react'
SplashScreen.preventAutoHideAsync()
export default function RootLayout() { const [loaded] = useFonts({ SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'), })
useEffect(() => { if (loaded) SplashScreen.hideAsync() }, [loaded])
if (!loaded) return null
return (
Tab layout z ikonami
```typescript // app/(tabs)/_layout.tsx import { Tabs } from 'expo-router' import { Ionicons } from '@expo/vector-icons'
export default function TabLayout() {
return (
Dynamiczne segmenty
```typescript // app/profile/[id].tsx import { useLocalSearchParams, useRouter } from 'expo-router' import { View, Text, Button, StyleSheet } from 'react-native'
export default function ProfileScreen() { const { id } = useLocalSearchParams<{ id: string }>() const router = useRouter()
return (
const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center' }, title: { fontSize: 20, marginBottom: 16 }, }) ```
Nawigacja do dynamicznego segmentu:
```typescript import { router } from 'expo-router'
// Przejście do /profile/123 router.push('/profile/123')
// Lub komponent Link import { Link } from 'expo-router' Zobacz profil ```
Typed routes: bezpieczeństwo typów w nawigacji
Expo Router 3 wprowadza **typed routes** — TypeScript wie o wszystkich twoich trasach w czasie kompilacji.
Włącz w `app.json`:
```json { "expo": { "experiments": { "typedRoutes": true } } } ```
Następnie uruchom `npx expo start` — Expo wygeneruje plik `expo-router/types` z wszystkimi trasami. Od tej chwili:
```typescript import { router } from 'expo-router'
// TypeScript złapie błąd jeśli trasa nie istnieje router.push('/profile/123') // OK router.push('/profil/123') // Błąd TypeScript! router.push({ pathname: '/profile/[id]', params: { id: '123' } }) // OK z typowaniem ```
Deep linking z app.json
Deep linking w Expo Router jest prawie automatyczny. Konfigurujesz scheme w `app.json`:
```json { "expo": { "scheme": "myapp", "android": { "intentFilters": [ { "action": "VIEW", "autoVerify": true, "data": [{ "scheme": "https", "host": "myapp.pl" }], "category": ["BROWSABLE", "DEFAULT"] } ] } } } ```
Po tej konfiguracji link `myapp://profile/123` lub `https://myapp.pl/profile/123` automatycznie otworzy aplikację na ekranie `/profile/123`. Expo Router mapuje URL na pliki bez żadnej dodatkowej konfiguracji.
Kiedy nadal wybrać React Navigation
Expo Router nie jest dla każdego projektu. Użyj React Navigation gdy:
- Projekt istnieje i ma zakorzenioną nawigację w React Navigation — migracja to duży koszt - Potrzebujesz zaawansowanych animacji przejść między ekranami (np. `react-native-screens` z niestandardowymi gestami) - Aplikacja nie używa Expo (bare React Native bez Expo SDK) - Masz bardzo niestandardową logikę nawigacyjną, której file-based model nie obsłuży elegancko
Expo Router jest natomiast oczywistym wyborem dla nowych projektów Expo, projektów z Expo Web, aplikacji gdzie URL sharing jest ważny.
Podsumowanie
Expo Router 3 to dojrzałe, produkcyjne rozwiązanie nawigacji dla React Native. File-based routing eliminuje połowę boilerplate'u, typed routes dają pełne bezpieczeństwo typów, a deep linking działa prawie samo. Jeśli zaczynasz nowy projekt z Expo — nie ma powodu sięgać po React Navigation od razu.
Potrzebujesz podobnego rozwiązania?
Porozmawiajmy o Twoim projekcie
Pierwsza rozmowa jest bezpłatna. Opisz nam swój pomysł — odpowiemy w ciągu jednego dnia roboczego.
Umów bezpłatną rozmowę