Expo Router di React Native: Panduan Lengkap Navigasi Berbasis File

Panduan lengkap Expo Router untuk React Native mencakup routing berbasis file, layout, rute dinamis, navigasi bertipe, dan pola lanjutan seperti modal dan tab.

Expo Router file-based navigation system for React Native mobile applications

Expo Router menghadirkan routing berbasis file ke React Native, menggantikan konfigurasi navigasi manual dengan pendekatan berbasis konvensi yang terinspirasi dari Next.js. Sejak Expo SDK 57 dan Expo Router, pengembangan navigasi lintas platform untuk Android, iOS, dan web hanya memerlukan pembuatan file di direktori yang tepat.

Memulai dengan Cepat

Proyek Expo baru sudah menyertakan Expo Router secara default. Perintah npx create-expo-app@latest --template default menghasilkan proyek yang telah dikonfigurasi dengan routing berbasis file. Untuk proyek yang sudah ada, cukup instal package expo-router dan perbarui entry point aplikasi.

Cara Kerja Routing Berbasis File di Expo Router

Setiap file yang ditempatkan di dalam direktori app secara otomatis menjadi sebuah rute. Jalur file berkorespondensi langsung dengan jalur URL, sehingga menghilangkan kebutuhan akan file konfigurasi terpusat. File app/settings.tsx menciptakan rute /settings, sedangkan app/profile/edit.tsx berkorespondensi dengan /profile/edit.

Pendekatan ini menawarkan tiga keunggulan utama dibandingkan konfigurasi tradisional menggunakan React Navigation:

  • Tanpa konfigurasi: rute terbentuk secara otomatis begitu file dibuat
  • Deep linking otomatis: setiap layar memiliki URL tersendiri, memudahkan proses berbagi dan pengujian
  • Navigasi bertipe: TypeScript mengenali rute yang tersedia sejak tahap kompilasi
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 },
})

Komponen Link menangani navigasi di semua platform. Di web, komponen ini menghasilkan elemen anchor dengan atribut href yang sesuai untuk keperluan SEO. Di platform native, komponen ini memicu navigasi bertipe stack.

Struktur Proyek dan File Layout

Expo Router menggunakan file _layout.tsx untuk mendefinisikan kontainer navigasi. Setiap direktori dapat memiliki layout-nya sendiri, sehingga memungkinkan terciptanya hierarki navigasi bersarang. Layout root membungkus keseluruhan aplikasi, sementara layout bersarang mengontrol bagian-bagian tertentu.

Berikut adalah struktur proyek yang umum digunakan:

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

Grup rute, yaitu direktori yang diapit tanda kurung, berfungsi untuk mengelompokkan file tanpa memengaruhi URL. Direktori (tabs) di atas membentuk navigator tab, namun URL tetap menjadi /home, /search, dan /profile, bukan /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>
  )
}

Layout root juga berperan sebagai titik inisialisasi untuk pemuatan font, pengaturan provider, dan konfigurasi global lainnya.

Membangun Navigasi Tab dengan Expo Router

Navigasi tab memerlukan file _layout.tsx di dalam sebuah grup rute. Expo Router menyediakan NativeTabs untuk pengalaman tab spesifik platform dan komponen standar Tabs untuk sebagian besar kebutuhan. Sejak SDK 56, Native Tabs mendukung properti disabled untuk mencegah pemilihan tab sambil tetap menampilkannya.

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

Setiap file layar tab mengekspor komponen React standar. Ikon, label, dan badge pada tab bar dikonfigurasi melalui prop options di layout.

Rute Dinamis dan Parameter Rute

Segmen dinamis menggunakan tanda kurung siku pada nama file. File bernama [id].tsx mencocokkan satu segmen, sedangkan [...slug].tsx menangkap seluruh segmen yang tersisa pada jalur URL.

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

Mengakses /product/42 akan menampilkan layar ini dengan variabel id bernilai "42". Hook useLocalSearchParams menyediakan akses bertipe terhadap seluruh parameter rute.

Untuk rute catch-all, [...slug].tsx menangkap segmen jalur secara keseluruhan:

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

Siap menguasai wawancara React Native Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Typed Routes: Keamanan pada Tahap Kompilasi

Expo Router secara otomatis menghasilkan tipe rute ketika fitur typed routes diaktifkan. Pemeriksaan ini mendeteksi tautan yang rusak pada tahap kompilasi, bukan saat aplikasi dijalankan.

Aktivasi dilakukan melalui file app.json:

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

Setelah diaktifkan, prop href pada komponen Link dan argumen pada router.push() hanya menerima string yang sesuai dengan rute yang valid:

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

Fitur typed routes bekerja sangat baik dengan useLocalSearchParams. Tipe yang dihasilkan menjamin kecocokan antara nama parameter pada definisi rute dan parameter yang digunakan oleh komponen pemanggil, sehingga mencegah bug halus yang baru muncul saat navigasi ke layar tertentu.

Layar Modal dan Opsi Presentasi

Modal di Expo Router merupakan layar biasa yang dikonfigurasi dengan presentation: 'modal' pada layout. Pendekatan ini tetap menghormati konvensi routing berbasis file: modal pada dasarnya hanyalah sebuah rute seperti rute lainnya.

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

Mengakses /create-post akan memicu presentasi modal. Pemanggilan router.back() menutup modal dan mengembalikan pengguna ke layar sebelumnya dalam tumpukan navigasi.

Selain komponen Link, Expo Router menyediakan API imperatif melalui objek router. API ini memungkinkan pengelolaan navigasi yang dipicu oleh logika bisnis, bukan hanya oleh interaksi langsung pengguna.

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
}

Perbedaan antara router.push dan router.replace sangat krusial dalam alur autentikasi di React Native. Setelah login berhasil, router.replace('/dashboard') mencegah pengguna kembali ke layar login melalui tombol kembali.

Navigasi dan Pengalihan

router.replace() menggantikan entri saat ini dalam riwayat navigasi. Untuk guard autentikasi yang mengalihkan pengguna yang belum login, disarankan menggunakan <Redirect href="/login" /> di dalam render komponen. Pendekatan ini bekerja pada fase render dan berfungsi dengan baik bersama server-side rendering di web.

Middleware dan Proteksi Rute

Expo Router menyediakan middleware server untuk logika pada level rute. File +middleware.ts mencegat permintaan sebelum mencapai komponen rute, memungkinkan pemeriksaan autentikasi, pengalihan, dan manipulasi header HTTP.

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
}

Pada platform native, middleware server tidak dijalankan. Proteksi rute mengandalkan guard di sisi klien. Pola yang paling umum digunakan adalah membungkus layout yang dilindungi dengan pemeriksaan autentikasi:

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 />
}
Perbedaan Perilaku Antar Platform

Middleware server hanya berjalan di web dengan server-side rendering. Untuk aplikasi native, implementasi guard di sisi klien pada komponen layout bersifat wajib. Mengombinasikan kedua pendekatan ini menjamin proteksi yang konsisten di seluruh platform.

Migrasi SDK 56: Fork React Navigation

Sejak Expo SDK 56, Expo Router tidak lagi bergantung pada package @react-navigation/*. Jika proyek mengimpor langsung dari React Navigation, perbarui import tersebut:

Package AsliLokasi Baru
@react-navigation/nativeexpo-router/react-navigation
@react-navigation/stackexpo-router/js-stack
@react-navigation/bottom-tabsexpo-router/js-tabs

Jalankan codemod untuk memperbarui import secara otomatis:

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

API runtime tetap sama. Hanya module specifier yang berpindah. Library pihak ketiga yang masih mengimpor dari @react-navigation/core tetap berfungsi karena Expo CLI menulis ulang import tersebut saat bundling.

Sumber Referensi

Yang Perlu Diketahui untuk Wawancara tentang Expo Router

  • Expo Router memetakan setiap file dalam direktori app ke sebuah rute secara otomatis, menghilangkan konfigurasi navigasi manual
  • Layout yang didefinisikan melalui file _layout.tsx membentuk hierarki navigasi untuk stack, tab, dan drawer
  • Rute dinamis dengan [param].tsx dan rute catch-all dengan [...slug].tsx menangani navigasi berparameter dengan dukungan TypeScript penuh
  • Typed routes mendeteksi tautan navigasi yang rusak pada tahap kompilasi saat diaktifkan di app.json
  • Layar modal, navigasi programatik melalui router.push/replace/back, dan middleware server melengkapi perangkat routing
  • Grup rute menggunakan direktori bertanda kurung mengelompokkan kode tanpa memengaruhi URL
  • Guard di sisi klien pada layout menangani autentikasi di platform native, sementara middleware server mencakup web
  • Sejak SDK 56, import dari @react-navigation/* harus diperbarui ke padanan expo-router/*

Untuk materi lebih lanjut tentang arsitektur React Native dan persiapan wawancara, lihat modul native modules.

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Tantangan harian

Bisakah kamu menemukan bug di React Native?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri SharpSkill

Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.

Diperbarui 6 Oktober 2026

Tag

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

Bagikan

Artikel terkait