Expo Router in React Native: Complete Handleiding voor Bestandsgebaseerde Navigatie

Expo Router voor React Native: bestandsgebaseerde routering, layouts, dynamische routes, getypeerde navigatie, tabs, modals, middleware en SDK 56 migratie.

Bestandsgebaseerd navigatiesysteem van Expo Router voor mobiele React Native-applicaties

Expo Router brengt bestandsgebaseerde routering naar React Native en vervangt handmatige navigatieconfiguratie door een conventiegestuurde aanpak geïnspireerd door Next.js. Vanaf Expo SDK 57 en Expo Router vereist het bouwen van platformonafhankelijke navigatie voor Android, iOS en web niets meer dan het aanmaken van bestanden in de juiste map.

Snel aan de slag

Nieuwe Expo-projecten worden geleverd met vooraf geconfigureerde Expo Router. Het commando npx create-expo-app@latest --template default genereert een project met bestandsgebaseerde routering direct klaar voor gebruik. Bestaande projecten kunnen dit toevoegen door expo-router te installeren en het ingangspunt bij te werken.

Hoe bestandsgebaseerde routering werkt in Expo Router

Elk bestand in de app-map wordt automatisch een route. Het bestandspad komt direct overeen met het URL-pad, waardoor een gecentraliseerde navigatieconfiguratie overbodig wordt. Een bestand op app/settings.tsx creëert een /settings-route, terwijl app/profile/edit.tsx overeenkomt met /profile/edit.

Deze aanpak biedt drie belangrijke voordelen ten opzichte van traditionele React Navigation-configuratie:

  • Geen configuratie: routes bestaan zodra een bestand wordt aangemaakt
  • Automatische deep linking: elk scherm krijgt een URL, wat delen en testen mogelijk maakt
  • Type-veilige navigatie: TypeScript kent de bestaande routes tijdens compilatie
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 },
})

Het Link-component verzorgt navigatie op alle platforms. Op het web rendert het een anchor-tag met correcte href-attributen voor SEO. Op native platforms activeert het stack-gebaseerde navigatie.

Projectstructuur en layoutbestanden

Expo Router gebruikt _layout.tsx-bestanden om navigatiecontainers te definiëren. Elke map kan zijn eigen layout hebben, waardoor geneste navigatiehiërarchieën ontstaan. De root-layout omvat de gehele app, terwijl geneste layouts specifieke secties aansturen.

Een typische projectstructuur ziet er als volgt uit:

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

Routegroepen, mappen omgeven door haakjes, organiseren bestanden zonder de URL te beïnvloeden. De map (tabs) hierboven creëert een tab-navigator, maar de URL's blijven /home, /search en /profile in plaats van /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>
  )
}

De root-layout dient ook als plek om lettertypen te laden, providers te initialiseren en globale instellingen te configureren.

Tab-navigatie bouwen met Expo Router

Tab-navigatie vereist een _layout.tsx-bestand binnen een routegroep. Expo Router biedt zowel NativeTabs voor platformspecifieke tab-ervaringen als het standaard Tabs-component voor de meeste toepassingen. Sinds SDK 56 ondersteunt NativeTabs de disabled-eigenschap om tabselectie te voorkomen terwijl tabs zichtbaar blijven.

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

Elk tab-schermbestand exporteert een standaard React-component. Het tab-bar-icoon, label en badge worden geconfigureerd via de options-prop in de layout.

Dynamische routes en routeparameters

Dynamische segmenten gebruiken vierkante haken in de bestandsnaam. Een bestand genaamd [id].tsx vangt een enkel segment op, terwijl [...slug].tsx alle resterende segmenten opvangt.

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

Navigeren naar /product/42 rendert dit scherm met id ingesteld op "42". De hook useLocalSearchParams biedt getypeerde toegang tot alle routeparameters.

Voor catch-all routes vangt [...slug].tsx volledige padsegmenten op:

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

Klaar om je React Native gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Getypeerde routes voor compileertijdveiligheid

Expo Router genereert automatisch routetypes wanneer typed routes is ingeschakeld. Dit detecteert defecte links tijdens compilatie in plaats van tijdens runtime.

Getypeerde routes inschakelen in app.json:

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

Eenmaal ingeschakeld accepteert de href-prop op Link en het argument voor router.push() alleen geldige routestrings:

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

Getypeerde routes werken goed samen met useLocalSearchParams. De gegenereerde types zorgen ervoor dat parameternamen overeenkomen tussen de routedefinitie en het consumerende component, waardoor subtiele bugs worden voorkomen die anders pas bij navigatie naar een specifiek scherm aan het licht zouden komen.

Modale schermen en presentatieopties

Modals in Expo Router zijn reguliere schermen geconfigureerd met presentation: 'modal' in de layout. Deze aanpak behoudt de bestandsgebaseerde conventie: een modal is gewoon een andere route.

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

Navigeren naar /create-post activeert de modale presentatie. De aanroep router.back() sluit de modal en keert terug naar het vorige scherm in de stack.

Programmatische navigatie en de Router-API

Naast het Link-component biedt Expo Router een imperatieve API via het router-object. Deze behandelt navigatie die wordt geactiveerd door bedrijfslogica in plaats van gebruikersinteracties.

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
}

Het onderscheid tussen router.push en router.replace is belangrijk voor authenticatiestromen in React Native. Na een succesvolle aanmelding voorkomt router.replace('/dashboard') dat de gebruiker terug kan navigeren naar het inlogscherm.

Navigatie vs. Redirect

router.replace() vervangt het huidige item in de navigatiegeschiedenis. Voor authenticatieguards die niet-geauthenticeerde gebruikers omleiden, gebruik <Redirect href="/login" /> binnen de componentrender: het wordt geactiveerd tijdens de renderfase en werkt correct met server-side rendering op het web.

Middleware en routebeveiliging

Expo Router biedt server-middleware voor logica op routeniveau. Het bestand +middleware.ts onderschept verzoeken voordat ze het routecomponent bereiken, waardoor authenticatiecontroles, omleidingen en headeraanpassingen mogelijk worden.

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
}

Op native platforms waar server-middleware niet draait, is routebeveiliging afhankelijk van client-side guards. Een gangbaar patroon omhult beschermde layouts met een authenticatiecontrole:

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 />
}
Platformbewustzijn

Server-middleware draait alleen op het web met server-side rendering. Voor native apps moet altijd client-side routebeveiliging worden geïmplementeerd in layoutcomponenten. Het combineren van beide benaderingen zorgt voor consistente beveiliging op alle platforms.

SDK 56 Migratie: React Navigation Fork

Vanaf Expo SDK 56 is Expo Router niet langer afhankelijk van @react-navigation/*-pakketten. Als het project rechtstreeks uit React Navigation importeert, moeten de imports worden bijgewerkt:

Oorspronkelijk pakketNieuwe locatie
@react-navigation/nativeexpo-router/react-navigation
@react-navigation/stackexpo-router/js-stack
@react-navigation/bottom-tabsexpo-router/js-tabs

De codemod werkt imports automatisch bij:

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

De runtime-API blijft ongewijzigd. Alleen de modulespecificaties zijn verplaatst. Third-party bibliotheken die nog steeds importeren uit @react-navigation/core werken automatisch omdat Expo CLI die imports herschrijft tijdens bundling.

Bronnen

Wat interviewers verwachten over Expo Router

  • Expo Router koppelt elk bestand in de app-map automatisch aan een route, waardoor handmatige navigatieconfiguratie overbodig wordt
  • Layouts gedefinieerd via _layout.tsx-bestanden creëren navigatiehiërarchieën voor stacks, tabs en drawers
  • Dynamische routes met [param].tsx en catch-all routes met [...slug].tsx verwerken geparametriseerde navigatie met volledige TypeScript-ondersteuning
  • Getypeerde routes detecteren defecte navigatielinks tijdens compilatie wanneer ingeschakeld in app.json
  • Modale schermen, programmatische navigatie via router.push/replace/back en server-middleware completeren de routeringstoolkit
  • Routegroepen met haakjes rond mapnamen organiseren code zonder URL's te beïnvloeden
  • Client-side routeguards in layouts verzorgen authenticatie op native platforms, terwijl server-middleware het web afdekt
  • Sinds SDK 56 moeten imports uit @react-navigation/* worden bijgewerkt naar expo-router/*-equivalenten

Voor meer over React Native-architectuur en sollicitatievoorbereidingen, zie de native modules module.

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Dagelijkse challenge

Zie jij de bug in React Native?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 6 oktober 2026

Tags

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

Delen

Gerelateerde artikelen