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

Kompletny przewodnik po Expo Router w React Native — nawigacja oparta na plikach, trasy dynamiczne, zakładki, modale i ochrona tras w 2026 roku.

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. Od wersji Expo SDK 55 i Expo Router v6, 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@sdk-55, 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ń — zastępując tradycyjny plik App.tsx jako punkt wejścia.

Budowanie nawigacji zakładkowej z Expo Router

Nawigacja zakładkowa wymaga pliku _layout.tsx wewnątrz grupy tras. Expo Router v6 wprowadza NativeTabs dla doświadczeń zakładkowych specyficznych dla platformy, ale standardowy komponent Tabs z Expo Router sprawdza się w większości przypadków.

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. 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 v6 wprowadza 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.

Podsumowanie

  • Expo Router v6 zastępuje ręczną konfigurację nawigacji konwencjami opartymi na plikach, automatycznie mapując każdy plik w katalogu app na trasę
  • Layouty definiowane przez pliki _layout.tsx tworzą hierarchie nawigacji — stosy, zakładki i szuflady — bez centralnej konfiguracji
  • 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, utrzymując czystą strukturę plików w miarę rozwoju aplikacji
  • Zabezpieczenia tras po stronie klienta w layoutach obsługują uwierzytelnianie na platformach natywnych, a middleware serwerowe pokrywa sieć web — oba podejścia łącznie zapewniają ochronę na wszystkich platformach

Tagi

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

Udostępnij

Powiązane artykuły