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.

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.
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
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:
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/privacyRoutegroepen, 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.
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.
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.
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:
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:
{
"expo": {
"experiments": {
"typedRoutes": true
}
}
}Eenmaal ingeschakeld accepteert de href-prop op Link en het argument voor router.push() alleen geldige routestrings:
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.
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>
)
}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.
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.
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.
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:
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 />
}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 pakket | Nieuwe locatie |
|---|---|
@react-navigation/native | expo-router/react-navigation |
@react-navigation/stack | expo-router/js-stack |
@react-navigation/bottom-tabs | expo-router/js-tabs |
De codemod werkt imports automatisch bij:
npx expo-codemod sdk-56-expo-router-react-navigation-replace srcDe 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
- Expo Router Documentatie - officiële handleiding en API-referentie
- Expo SDK 57 Changelog - actuele stabiele release-notes
- Expo Router v56 Blogpost - uitleg over de React Navigation fork
- SDK 55 naar 56 Migratiegids - importmigratiestappen
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].tsxen catch-all routes met[...slug].tsxverwerken 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/backen 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 naarexpo-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.
Zie jij de bug in React Native?
Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Geschreven door
Anthony Fillion-MailletOprichter 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
Delen
Gerelateerde artikelen

React Native 0.87 en SwiftPM in 2026: Modern iOS Build Systeem en Sollicitatievragen
React Native 0.87 introduceert experimentele Swift Package Manager ondersteuning voor iOS builds. Deze gids behandelt SwiftPM configuratie, de Strict TypeScript API, Metro 0.87 optimalisaties en sollicitatievragen voor senior mobile developers.

Hermes V1 in React Native 0.84: Performance, Voorgecompileerde Bytecode en Technische Sollicitatievragen
Een diepgaande analyse van Hermes V1 als standaard JavaScript-engine in React Native 0.84. Dit artikel behandelt bytecode-voorcompilatie, de Hades concurrent garbage collector, geheugenoptimalisatiestrategieën en technische sollicitatievragen.

React Native 0.86 in 2026: Edge-to-Edge Android, DevTools en Sollicitatievragen
Beheers React Native 0.86 met edge-to-edge ondersteuning voor Android 15+, DevTools verbeteringen en praktische sollicitatievragen. Complete gids met codevoorbeelden.