Expo Router у React Native: Повний посібник з файлової навігації

Повний посібник з Expo Router у React Native — файлова навігація, динамічні маршрути, вкладки, модальні екрани та захист маршрутів. Актуальний гайд 2026 року.

Expo Router у React Native — файлова навігація

Expo Router впроваджує файлову маршрутизацію в React Native, замінюючи ручне налаштування навігації конвенційним підходом, натхненним Next.js. Починаючи з Expo SDK 55 та Expo Router v6, створення кросплатформної навігації для Android, iOS та веб зводиться до розміщення файлів у правильному каталозі.

Швидкий старт

Нові проєкти Expo постачаються з попередньо налаштованим Expo Router. Достатньо виконати npx create-expo-app@latest --template default@sdk-55, щоб розпочати роботу з файловою навігацією. Для існуючих проєктів необхідно встановити expo-router та оновити точку входу.

Як працює файлова маршрутизація в Expo Router

Кожен файл у каталозі app автоматично стає маршрутом. Шлях до файлу безпосередньо відповідає URL-шляху, усуваючи потребу в централізованому налаштуванні навігації. Файл app/settings.tsx створює маршрут /settings, а app/profile/edit.tsx відповідає маршруту /profile/edit.

Такий підхід пропонує три ключові переваги порівняно з традиційним налаштуванням React Navigation:

  • Нульова конфігурація: маршрути з'являються в момент створення файлу
  • Автоматичний deep linking: кожен екран отримує URL, що забезпечує можливість поширення та тестування
  • Типізована навігація: TypeScript знає, які маршрути існують на етапі компіляції
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 },
})

Компонент Link забезпечує навігацію на всіх платформах. У вебі він рендериться як тег <a> з відповідним атрибутом href для SEO. На нативних платформах він ініціює стекову навігацію.

Структура проєкту та файли макетів

Expo Router використовує файли _layout.tsx для визначення контейнерів навігації. Кожен каталог може мати власний макет, створюючи вкладені ієрархії навігації. Кореневий макет обгортає весь застосунок, тоді як вкладені макети контролюють окремі розділи.

Типова структура проєкту виглядає наступним чином:

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

Групи маршрутів — каталоги, загорнуті в дужки — організовують файли без впливу на URL. Каталог (tabs) вище створює навігатор вкладок, але URL-адреси залишаються у вигляді /home, /search та /profile, а не /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>
  )
}

Кореневий макет також є місцем для завантаження шрифтів, ініціалізації провайдерів та налаштування глобальних параметрів — замінюючи традиційний файл App.tsx як точку входу.

Створення навігації вкладками з Expo Router

Навігація вкладками потребує файлу _layout.tsx всередині групи маршрутів. Expo Router v6 представляє NativeTabs для платформоспецифічного досвіду вкладок, але стандартний компонент Tabs з Expo Router покриває більшість випадків використання.

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>
  )
}

Кожен файл екрану вкладки експортує стандартний React-компонент. Іконка, мітка та значок панелі вкладок налаштовуються через властивість options у макеті.

Динамічні маршрути та параметри маршрутів

Динамічні сегменти використовують квадратні дужки в назві файлу. Файл із назвою [id].tsx відповідає будь-якому окремому сегменту, тоді як [...slug].tsx перехоплює всі наступні сегменти.

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' },
})

При переході до /product/42 цей екран рендериться з параметром id, встановленим у "42". Хук useLocalSearchParams забезпечує типізований доступ до всіх параметрів маршруту.

Для маршрутів типу catch-all файл [...slug].tsx перехоплює цілі сегменти шляху:

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('/')} />
}

Готовий до співбесід з React Native?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Типізовані маршрути для безпеки на етапі компіляції

Expo Router автоматично генерує типи маршрутів при увімкненій опції typed routes. Це дозволяє виявляти некоректні посилання на етапі компіляції, а не під час виконання.

Активація типізованих маршрутів у файлі app.json:

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

Після активації властивість href компонента Link та аргумент router.push() приймають лише валідні рядки маршрутів:

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')
}

Типізовані маршрути ідеально поєднуються з useLocalSearchParams. Згенеровані типи гарантують відповідність імен параметрів між визначенням маршруту та компонентом, що його використовує, запобігаючи неочевидним помилкам, які проявляються лише при переході на конкретний екран.

Модальні екрани та параметри презентації

Модальні вікна в Expo Router — це звичайні екрани, налаштовані з параметром presentation: 'modal' у макеті. Такий підхід зберігає файлову конвенцію — модальне вікно є просто ще одним маршрутом.

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,
  },
})

При переході до /create-post активується модальна презентація. Виклик router.back() закриває модальне вікно, повертаючись до попереднього екрану в стеку.

Програмна навігація та API маршрутизатора

Окрім компонента Link, Expo Router надає імперативний API через об'єкт router. Він обробляє навігацію, ініційовану бізнес-логікою, а не безпосередньою взаємодією користувача.

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
}

Різниця між router.push та router.replace має значення при потоках автентифікації. Після успішного входу router.replace('/dashboard') запобігає поверненню до екрану входу.

Навігація та перенаправлення

router.replace() замінює поточний запис в історії навігації. Для механізмів захисту, що перенаправляють неавтентифікованих користувачів, слід використовувати <Redirect href="/login" /> всередині рендеру компонента — він спрацьовує на етапі рендерингу та коректно працює з серверним рендерингом.

Middleware та захист маршрутів

Expo Router v6 впроваджує серверне middleware для логіки на рівні маршрутів. Файл +middleware.ts перехоплює запити до їх потрапляння до компонента маршруту, забезпечуючи перевірку автентифікації, перенаправлення та маніпуляцію заголовками.

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
}

На нативних платформах, де серверне middleware не працює, захист маршрутів спирається на клієнтські механізми. Поширений патерн полягає в обгортанні захищених макетів перевіркою автентифікації:

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 />
}
Врахування платформ

Серверне middleware працює виключно у вебі з серверним рендерингом. Для нативних застосунків завжди слід реалізовувати клієнтський захист маршрутів у компонентах макетів. Поєднання обох підходів гарантує послідовний захист на всіх платформах.

Висновок

  • Expo Router v6 замінює ручне налаштування навігації файловими конвенціями, автоматично перетворюючи кожен файл у каталозі app на маршрут
  • Макети, визначені через файли _layout.tsx, створюють ієрархії навігації — стеки, вкладки та бічні панелі — без централізованої конфігурації
  • Динамічні маршрути з [param].tsx та маршрути catch-all з [...slug].tsx забезпечують параметризовану навігацію з повною підтримкою TypeScript
  • Типізовані маршрути виявляють некоректні навігаційні посилання на етапі компіляції після увімкнення в app.json
  • Модальні екрани, програмна навігація через router.push/replace/back та серверне middleware доповнюють інструментарій маршрутизації
  • Групи маршрутів із каталогами в дужках організовують код без впливу на URL-адреси, зберігаючи чисту структуру файлів при зростанні застосунку
  • Клієнтський захист маршрутів у макетах забезпечує автентифікацію на нативних платформах, а серверне middleware покриває веб — обидва підходи разом створюють надійний захист для кросплатформних застосунків

Теги

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

Поділитися

Пов'язані статті