Expo Router w React Native: Kompletny przewodnik po nawigacji opartej na plikach

Kompletny przewodnik po Expo Router w React Native — nawigacja oparta na plikach, layouty, trasy dynamiczne, typowana nawigacja oraz zaawansowane wzorce jak modale i zakładki.

Expo Router w React Native — nawigacja oparta na plikach

Expo Router wprowadza do React Native nawigację opartą na strukturze plików, zastępując ręczną konfigurację nawigacji podejściem konwencyjnym inspirowanym Next.js. Począwszy od Expo SDK 57 i Expo Router, budowanie wieloplatformowej nawigacji dla Androida, iOS-a i aplikacji webowych sprowadza się do tworzenia plików w odpowiednim katalogu.

Szybki start

Nowe projekty Expo są dostarczane z wstępnie skonfigurowanym Expo Routerem. Wystarczy uruchomić npx create-expo-app@latest --template default, aby rozpocząć pracę z nawigacją opartą na plikach. W istniejących projektach wystarczy zainstalować expo-router i zaktualizować punkt wejścia aplikacji.

Jak działa nawigacja oparta na plikach w Expo Router

Każdy plik w katalogu app automatycznie staje się trasą. Ścieżka pliku mapuje się bezpośrednio na ścieżkę URL, eliminując potrzebę centralnej konfiguracji nawigacji. Plik app/settings.tsx tworzy trasę /settings, a app/profile/edit.tsx mapuje się na /profile/edit.

Takie podejście oferuje trzy kluczowe zalety w porównaniu z tradycyjną konfiguracją React Navigation:

  • Zerowa konfiguracja: trasy istnieją w momencie utworzenia pliku
  • Automatyczne deep linking: każdy ekran otrzymuje URL, co umożliwia udostępnianie i testowanie
  • Typowana nawigacja: TypeScript wie, które trasy istnieją w czasie kompilacji
app/index.tsxtypescript
import { View, Text, StyleSheet } from 'react-native'
import { Link } from 'expo-router'

export default function HomeScreen() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome</Text>
      {/* Link maps directly to file path */}
      <Link href="/settings" style={styles.link}>
        Open Settings
      </Link>
      <Link href="/profile/edit" style={styles.link}>
        Edit Profile
      </Link>
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', padding: 24 },
  title: { fontSize: 28, fontWeight: 'bold', marginBottom: 16 },
  link: { fontSize: 16, color: '#61DAFB', marginTop: 12 },
})

Komponent Link obsługuje nawigację na wszystkich platformach. W przeglądarce renderuje tag <a> z odpowiednim atrybutem href dla SEO. Na platformach natywnych uruchamia nawigację opartą na stosie.

Struktura projektu i pliki layoutu

Expo Router wykorzystuje pliki _layout.tsx do definiowania kontenerów nawigacji. Każdy katalog może mieć własny layout, tworząc zagnieżdżone hierarchie nawigacji. Layout główny opakowuje całą aplikację, natomiast zagnieżdżone layouty kontrolują poszczególne sekcje.

Typowa struktura projektu wygląda następująco:

text
app/
  _layout.tsx          # Root layout (Stack or custom)
  index.tsx            # Home screen (/)
  (tabs)/              # Tab group (parentheses = route group)
    _layout.tsx        # Tab navigator
    home.tsx           # /home tab
    search.tsx         # /search tab
    profile.tsx        # /profile tab
  settings/
    _layout.tsx        # Settings stack layout
    index.tsx          # /settings
    notifications.tsx  # /settings/notifications
    privacy.tsx        # /settings/privacy

Grupy tras — katalogi ujęte w nawiasy — organizują pliki bez wpływu na URL. Katalog (tabs) powyżej tworzy nawigator zakładek, ale adresy URL pozostają w postaci /home, /search i /profile, a nie /tabs/home.

app/_layout.tsxtypescript
import { Stack } from 'expo-router'

export default function RootLayout() {
  return (
    <Stack
      screenOptions={{
        headerStyle: { backgroundColor: '#1a1a2e' },
        headerTintColor: '#ffffff',
        headerTitleStyle: { fontWeight: '600' },
      }}
    >
      <Stack.Screen name="index" options={{ title: 'Home' }} />
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen name="settings" options={{ title: 'Settings' }} />
    </Stack>
  )
}

Główny layout służy również jako miejsce do ładowania czcionek, inicjalizacji providerów i konfiguracji globalnych ustawień.

Budowanie nawigacji zakładkowej z Expo Router

Nawigacja zakładkowa wymaga pliku _layout.tsx wewnątrz grupy tras. Expo Router udostępnia zarówno NativeTabs dla doświadczeń zakładkowych specyficznych dla platformy, jak i standardowy komponent Tabs dla większości przypadków użycia. Od SDK 56 Native Tabs obsługuje właściwość disabled umożliwiającą zablokowanie wyboru zakładki przy jednoczesnym zachowaniu jej widoczności.

app/(tabs)/_layout.tsxtypescript
import { Tabs } from 'expo-router'
import { Ionicons } from '@expo/vector-icons'

export default function TabLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: '#61DAFB',
        tabBarInactiveTintColor: '#888',
        tabBarStyle: {
          backgroundColor: '#1a1a2e',
          borderTopColor: '#2d2d44',
        },
      }}
    >
      <Tabs.Screen
        name="home"
        options={{
          title: 'Home',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="home" size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="search"
        options={{
          title: 'Search',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="search" size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'Profile',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="person" size={size} color={color} />
          ),
        }}
      />
    </Tabs>
  )
}

Każdy plik ekranu zakładki eksportuje standardowy komponent React. Ikona, etykieta i znacznik paska zakładek konfigurowane są za pomocą właściwości options w layoucie.

Trasy dynamiczne i parametry tras

Dynamiczne segmenty wykorzystują nawiasy kwadratowe w nazwie pliku. Plik o nazwie [id].tsx dopasowuje dowolny pojedynczy segment, natomiast [...slug].tsx przechwytuje wszystkie pozostałe segmenty.

app/product/[id].tsxtypescript
import { View, Text, StyleSheet } from 'react-native'
import { useLocalSearchParams, Stack } from 'expo-router'

export default function ProductScreen() {
  // Extract the dynamic parameter from the URL
  const { id } = useLocalSearchParams<{ id: string }>()

  return (
    <View style={styles.container}>
      <Stack.Screen options={{ title: \`Product \${id}\` }} />
      <Text style={styles.heading}>Product Details</Text>
      <Text style={styles.id}>ID: {id}</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 24 },
  heading: { fontSize: 24, fontWeight: 'bold', marginBottom: 8 },
  id: { fontSize: 16, color: '#888' },
})

Przejście do /product/42 renderuje ten ekran z parametrem id ustawionym na "42". Hook useLocalSearchParams zapewnia typowany dostęp do wszystkich parametrów trasy.

Dla tras typu catch-all, [...slug].tsx przechwytuje całe segmenty ścieżki:

app/docs/[...slug].tsxtypescript
import { useLocalSearchParams } from 'expo-router'

export default function DocsScreen() {
  // /docs/getting-started/installation → slug = ['getting-started', 'installation']
  const { slug } = useLocalSearchParams<{ slug: string[] }>()

  return <DocViewer path={slug.join('/')} />
}

Gotowy na rozmowy o React Native?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Typowane trasy dla bezpieczeństwa w czasie kompilacji

Expo Router automatycznie generuje typy tras po włączeniu opcji typed routes. Pozwala to wychwytywać nieprawidłowe linki w czasie kompilacji, a nie w czasie wykonywania.

Aktywacja typowanych tras w pliku app.json:

json
{
  "expo": {
    "experiments": {
      "typedRoutes": true
    }
  }
}

Po włączeniu tej opcji, właściwość href komponentu Link oraz argument funkcji router.push() akceptują wyłącznie prawidłowe ciągi znaków tras:

app/checkout.tsxtypescript
import { router } from 'expo-router'

function handleCheckout(cartId: string) {
  // TypeScript validates this route exists
  router.push(\`/product/\${cartId}\`)

  // This would cause a compile error if /nonexistent doesn't exist
  // router.push('/nonexistent')
}

Typowane trasy doskonale współpracują z useLocalSearchParams. Wygenerowane typy zapewniają, że nazwy parametrów są spójne między definicją trasy a komponentem ją konsumującym, zapobiegając subtelnym błędom ujawniającym się dopiero podczas nawigacji do konkretnego ekranu.

Ekrany modalne i opcje prezentacji

Modale w Expo Router to zwykłe ekrany skonfigurowane z opcją presentation: 'modal' w layoucie. Takie podejście zachowuje konwencję opartą na plikach — modal to po prostu kolejna trasa.

app/_layout.tsxtypescript
import { Stack } from 'expo-router'

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="index" />
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      {/* Modal screen slides up from the bottom */}
      <Stack.Screen
        name="create-post"
        options={{
          presentation: 'modal',
          headerTitle: 'New Post',
        }}
      />
    </Stack>
  )
}
app/create-post.tsxtypescript
import { View, TextInput, Button, StyleSheet } from 'react-native'
import { router } from 'expo-router'
import { useState } from 'react'

export default function CreatePostModal() {
  const [title, setTitle] = useState('')

  const handleSubmit = () => {
    // Submit logic here
    router.back() // Dismiss the modal
  }

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        placeholder="Post title"
        value={title}
        onChangeText={setTitle}
      />
      <Button title="Publish" onPress={handleSubmit} />
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 24 },
  input: {
    borderWidth: 1,
    borderColor: '#333',
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
    marginBottom: 16,
  },
})

Przejście do /create-post uruchamia prezentację modalną. Wywołanie router.back() zamyka modal, wracając do poprzedniego ekranu w stosie.

Programowa nawigacja i API routera

Oprócz komponentu Link, Expo Router udostępnia imperatywne API przez obiekt router. Obsługuje ono nawigację wywoływaną logiką biznesową, a nie bezpośrednimi interakcjami użytkownika.

typescript
import { router } from 'expo-router'

// Push a new screen onto the stack
router.push('/profile/settings')

// Replace the current screen (no back button)
router.replace('/login')

// Go back to the previous screen
router.back()

// Navigate with parameters
router.push({
  pathname: '/product/[id]',
  params: { id: '42', source: 'recommendations' },
})

// Check if going back is possible
import { useRouter } from 'expo-router'

function BackButton() {
  const router = useRouter()
  return router.canGoBack() ? (
    <Button title="Back" onPress={() => router.back()} />
  ) : null
}

Różnica między router.push a router.replace jest istotna przy przepływach uwierzytelniania w React Native. Po pomyślnym logowaniu router.replace('/dashboard') uniemożliwia powrót do ekranu logowania.

Nawigacja a przekierowanie

router.replace() zastępuje bieżący wpis w historii nawigacji. Dla strażników uwierzytelniania, które przekierowują niezalogowanych użytkowników, należy użyć <Redirect href="/login" /> wewnątrz renderowania komponentu — uruchamia się ono w fazie renderowania i działa poprawnie z renderowaniem po stronie serwera.

Middleware i ochrona tras

Expo Router udostępnia middleware serwerowe dla logiki na poziomie tras. Plik +middleware.ts przechwytuje żądania przed dotarciem do komponentu trasy, umożliwiając sprawdzanie uwierzytelniania, przekierowania i manipulację nagłówkami.

app/+middleware.tstypescript
import { type MiddlewareRequest } from 'expo-router/server'

export function middleware(request: MiddlewareRequest) {
  const { pathname } = request.nextUrl

  // Protect dashboard routes
  const protectedPaths = ['/dashboard', '/settings', '/profile']
  const isProtected = protectedPaths.some(p => pathname.startsWith(p))

  if (isProtected) {
    const token = request.cookies.get('session')
    if (!token) {
      return Response.redirect(new URL('/login', request.url))
    }
  }

  return undefined // Continue to route
}

Na platformach natywnych, gdzie middleware serwerowe nie działa, ochrona tras opiera się na zabezpieczeniach po stronie klienta. Powszechnym wzorcem jest opakowywanie chronionych layoutów sprawdzaniem uwierzytelniania:

app/(authenticated)/_layout.tsxtypescript
import { Redirect, Stack } from 'expo-router'
import { useAuth } from '@/hooks/useAuth'

export default function AuthenticatedLayout() {
  const { isLoggedIn, isLoading } = useAuth()

  if (isLoading) return null
  if (!isLoggedIn) return <Redirect href="/login" />

  return <Stack />
}
Uwaga na platformy

Middleware serwerowe działa wyłącznie w sieci web z renderowaniem po stronie serwera. W aplikacjach natywnych należy zawsze implementować zabezpieczenia tras po stronie klienta w komponentach layoutu. Połączenie obu podejść gwarantuje spójną ochronę na wszystkich platformach.

Migracja SDK 56: Fork React Navigation

Począwszy od Expo SDK 56, Expo Router nie zależy już od pakietów @react-navigation/*. Jeśli projekt importuje bezpośrednio z React Navigation, należy zaktualizować importy:

Oryginalny pakietNowa lokalizacja
@react-navigation/nativeexpo-router/react-navigation
@react-navigation/stackexpo-router/js-stack
@react-navigation/bottom-tabsexpo-router/js-tabs

Codemod automatycznie aktualizuje importy:

bash
npx expo-codemod sdk-56-expo-router-react-navigation-replace src

API w czasie wykonywania pozostaje niezmienione. Zmienione zostały wyłącznie specyfikatory modułów. Biblioteki zewnętrzne nadal importujące z @react-navigation/core działają automatycznie, ponieważ Expo CLI przepisuje te importy podczas bundlowania.

Źródła

Czego rekruterzy oczekują w temacie Expo Router

  • Expo Router mapuje każdy plik w katalogu app na trasę automatycznie, eliminując ręczną konfigurację nawigacji
  • Layouty definiowane przez pliki _layout.tsx tworzą hierarchie nawigacji dla stosów, zakładek i szuflad
  • Trasy dynamiczne z [param].tsx i trasy catch-all z [...slug].tsx obsługują nawigację z parametrami z pełnym wsparciem TypeScript
  • Typowane trasy wykrywają nieprawidłowe linki nawigacyjne w czasie kompilacji po włączeniu w app.json
  • Ekrany modalne, programowa nawigacja przez router.push/replace/back oraz middleware serwerowe uzupełniają zestaw narzędzi nawigacyjnych
  • Grupy tras z katalogami w nawiasach organizują kod bez wpływu na adresy URL
  • Zabezpieczenia tras po stronie klienta w layoutach obsługują uwierzytelnianie na platformach natywnych, a middleware serwerowe pokrywa sieć web
  • Od SDK 56 importy z @react-navigation/* muszą być zaktualizowane do odpowiedników expo-router/*

Więcej informacji o architekturze React Native i przygotowaniu do rozmów kwalifikacyjnych znajduje się w module dotyczącym natywnych modułów.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Wyzwanie dnia

Znajdziesz błąd w React Native?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 6 października 2026

Tagi

#react-native
#expo
#expo-router
#navigation
#mobile-development
#tutorial

Udostępnij

Powiązane artykuły