Expo Router in React Native: Vollständiger Leitfaden zur dateibasierten Navigation

Expo Router revolutioniert die Navigation in React Native durch dateibasiertes Routing. Umfassender Leitfaden zu Projektstruktur, Tab-Navigation, dynamischen Routen, Typed Routes, Modalen, programmatischer Navigation, Middleware und Routenschutz.

Dateibasiertes Navigationssystem mit Expo Router in React Native

Die Navigation in React Native galt lange als einer der aufwändigsten Aspekte der mobilen Entwicklung. Navigatoren mussten manuell konfiguriert, Bildschirme einzeln registriert und Deep Links separat eingerichtet werden. Expo Router beseitigt diese Komplexität, indem es dateibasiertes Routing in die React-Native-Welt bringt. Das Prinzip ist ebenso einfach wie wirkungsvoll: Jede Datei im app-Verzeichnis wird automatisch zu einer navigierbaren Route. Was Entwickler aus Next.js oder Remix kennen, steht nun auch in nativen Anwendungen zur Verfügung. Seit dem Expo SDK 55 und Expo Router v6 deckt dieses System Android, iOS und das Web gleichermaßen ab, ohne dass plattformspezifischer Code erforderlich wäre. Deep Linking, typisierte Pfade und serverseitige Middleware ergeben sich als natürliche Nebenprodukte dieser Architektur.

Schnellstart

Neue Expo-Projekte enthalten Expo Router bereits standardmässig. Mit dem Befehl npx create-expo-app@latest --template default@sdk-55 entsteht ein vollständig vorkonfiguriertes Projekt mit dateibasiertem Routing. Für bestehende Projekte genügt die Installation des Pakets expo-router sowie die Anpassung des Einstiegspunkts in der Projektkonfiguration.

So funktioniert dateibasiertes Routing

Das zentrale Konzept hinter Expo Router lässt sich auf eine Grundregel reduzieren: Dateipfad gleich URL-Pfad. Eine Datei namens app/settings.tsx erzeugt die Route /settings. Eine Datei unter app/profile/edit.tsx wird unter /profile/edit erreichbar. Eine zentrale Routing-Konfiguration, wie sie React Navigation erfordert, entfällt vollständig.

Dieser Ansatz bietet gegenüber der traditionellen manuellen Konfiguration drei entscheidende Vorteile:

  • Keine Konfigurationsdateien: Routen existieren automatisch, sobald die entsprechende Datei angelegt wird
  • Automatisches Deep Linking: Jeder Bildschirm besitzt eine URL, was das Teilen von Inhalten und das Testen erheblich vereinfacht
  • Typisierte Navigation: TypeScript erkennt sämtliche verfügbaren Routen bereits zur Kompilierzeit und verhindert fehlerhafte Links
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 },
})

Die Link-Komponente stellt die plattformübergreifende Navigation sicher. Im Web generiert sie ein semantisch korrektes Anchor-Element mit href-Attribut, was der Suchmaschinenoptimierung zugutekommt. Auf nativen Plattformen löst sie eine Stack-Navigation mit den gewohnten Übergangsanimationen aus.

Projektstruktur und Layout-Dateien

Layouts bilden das Rückgrat der Navigationsarchitektur in Expo Router. Jedes Verzeichnis innerhalb von app kann eine eigene _layout.tsx-Datei enthalten, die den Navigationscontainer für alle darin befindlichen Bildschirme definiert. Das Root-Layout umschließt die gesamte Anwendung, während verschachtelte Layouts individuelle Bereiche steuern.

Eine typische Projektstruktur veranschaulicht dieses Prinzip:

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

Besondere Beachtung verdienen die Routengruppen -- Verzeichnisse, deren Namen in runden Klammern stehen. Das Verzeichnis (tabs) erstellt zwar einen Tab-Navigator, beeinflusst aber nicht die URL-Struktur. Die resultierenden Pfade lauten /home, /search und /profile, nicht etwa /(tabs)/home. Dieser Mechanismus erlaubt es, unterschiedliche Navigationsmuster auf verschiedene Anwendungsbereiche anzuwenden, ohne die URLs zu verändern.

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

Das Root-Layout übernimmt darüber hinaus die Funktion des traditionellen App.tsx als Einstiegspunkt. Hier werden Schriftarten geladen, globale Provider konfiguriert und anwendungsweite Einstellungen vorgenommen. Jeder Bildschirm, der innerhalb dieses Layouts gerendert wird, erbt die definierten Header-Optionen, sofern keine Überschreibungen auf Bildschirmebene vorliegen.

Tab-Navigation implementieren

Tab-Navigatoren gehören zu den am häufigsten eingesetzten Navigationsmustern in mobilen Anwendungen. In Expo Router erfolgt deren Konfiguration über die Tabs-Komponente innerhalb einer Routengruppe. Die Deklarationsreihenfolge der Tabs.Screen-Elemente bestimmt die Anordnung der Tabs in der Navigationsleiste.

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

Jede Datei innerhalb der Routengruppe exportiert eine reguläre React-Komponente. Icons, Beschriftungen und Badges der Tab-Leiste werden über die options-Prop des jeweiligen Tabs.Screen konfiguriert. Durch die Nutzung der @expo/vector-icons-Bibliothek stehen Hunderte vorgefertigte Icons zur Verfügung, die sich nahtlos in das Tab-Layout einfügen.

Dynamische Routen und Routenparameter

Statische Dateipfade reichen selten aus. Dynamische Segmente, gekennzeichnet durch eckige Klammern im Dateinamen, ermöglichen parametrisierte Navigation. Eine Datei [id].tsx fängt ein einzelnes Segment ab, während [...slug].tsx sämtliche verbleibenden Pfadsegmente in einem Array sammelt.

app/product/[id].tsxtypescript
import { View, Text, StyleSheet } from 'react-native'
import { useLocalSearchParams, Stack } from 'expo-router'

export default function ProductScreen() {
  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' },
})

Beim Aufruf von /product/42 rendert dieser Bildschirm mit dem Wert "42" für die Variable id. Der Hook useLocalSearchParams liefert typisierten Zugriff auf alle URL-Parameter und stellt sicher, dass die erwarteten Parametertypen eingehalten werden.

Catch-All-Routen erfassen variable Pfadtiefen und eignen sich besonders für Dokumentationssysteme oder verschachtelte Kategorie-Strukturen:

app/docs/[...slug].tsxtypescript
import { useLocalSearchParams } from 'expo-router'

export default function DocsScreen() {
  const { slug } = useLocalSearchParams<{ slug: string[] }>()

  return <DocViewer path={slug.join('/')} />
}

Der Pfad /docs/getting-started/installation erzeugt ein Array ['getting-started', 'installation'], das beliebig weiterverarbeitet werden kann. Diese Flexibilität macht Catch-All-Routen zur bevorzugten Wahl, wenn die Tiefe der URL-Hierarchie nicht im Voraus feststeht.

Bereit für deine React Native-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Typed Routes: Typsicherheit zur Kompilierzeit

Ein häufiges Problem in Navigationssystemen sind fehlerhafte Routen-Strings, die erst zur Laufzeit auffallen -- oft in Produktivumgebungen. Expo Router bietet mit Typed Routes eine Lösung, die TypeScript-Typen automatisch aus der vorhandenen Dateistruktur generiert. Ungültige Links werden so bereits beim Kompilieren als Fehler markiert.

Die Aktivierung erfolgt über die Projektkonfiguration in app.json:

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

Sobald diese Option aktiv ist, akzeptieren sowohl die href-Prop der Link-Komponente als auch router.push() ausschließlich Strings, die tatsächlich existierenden Routen entsprechen:

app/checkout.tsxtypescript
import { router } from 'expo-router'

function handleCheckout(cartId: string) {
  router.push(`/product/${cartId}`)
}

Die Kombination aus Typed Routes und useLocalSearchParams garantiert, dass Parameternamen in der Routendefinition und im konsumierenden Komponent übereinstimmen. Wird eine Routendatei umbenannt oder gelöscht, meldet TypeScript sofort alle veralteten Referenzen. In Projekten mit Dutzenden oder Hunderten von Bildschirmen spart diese Absicherung erheblich Zeit bei der Fehlersuche.

Modale Bildschirme und Präsentationsoptionen

Modale gehören zum Standardrepertoire mobiler Oberflächen. In Expo Router werden modale Bildschirme als gewöhnliche Routen behandelt, die im übergeordneten Layout mit der Option presentation: 'modal' konfiguriert werden. Die Dateikonvention bleibt gewahrt: Ein Modal ist nichts anderes als eine Route mit besonderer Darstellungsform.

app/_layout.tsx (modal)typescript
import { Stack } from 'expo-router'

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="index" />
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen
        name="create-post"
        options={{
          presentation: 'modal',
          headerTitle: 'New Post',
        }}
      />
    </Stack>
  )
}

Der zugehörige Bildschirm wird als reguläre Komponente implementiert. router.back() schließt das Modal und führt den Benutzer zum vorherigen Bildschirm im Navigationsstapel zurück:

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 = () => {
    router.back()
  }

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

Neben dem Standard-Modal unterstützt Expo Router die Varianten transparentModal, fullScreenModal und containedModal. Jede Variante unterscheidet sich in Animationsverhalten und Darstellung, wobei die plattformspezifischen Konventionen von iOS und Android respektiert werden.

Programmatische Navigation und die Router-API

Die deklarative Link-Komponente deckt die meisten Anwendungsfälle ab. Situationen, in denen die Navigation durch Geschäftslogik gesteuert wird -- etwa nach einem erfolgreichen Login, dem Absenden eines Formulars oder dem Ablauf eines Timers -- erfordern jedoch die imperative Router-API.

typescript
import { router } from 'expo-router'

router.push('/profile/settings')
router.replace('/login')
router.back()

router.push({
  pathname: '/product/[id]',
  params: { id: '42', source: 'recommendations' },
})

import { useRouter } from 'expo-router'

function BackButton() {
  const router = useRouter()
  return router.canGoBack() ? (
    <Button title="Back" onPress={() => router.back()} />
  ) : null
}

Die Unterscheidung zwischen router.push und router.replace spielt in Authentifizierungsabläufen eine besondere Rolle. Nach einer erfolgreichen Anmeldung verhindert router.replace('/dashboard'), dass Benutzer über die Zurück-Taste zum Login-Bildschirm gelangen. Die Methode router.canGoBack() ermöglicht darüber hinaus die bedingte Anzeige von Zurück-Schaltflächen, was insbesondere in tiefen Navigationshierarchien die Benutzererfahrung verbessert.

Navigation vs. Weiterleitung

router.replace() ersetzt den aktuellen Eintrag im Navigationsverlauf. Für Authentifizierungswächter, die nicht angemeldete Benutzer umleiten sollen, eignet sich die Komponente <Redirect href="/login" /> besser. Sie greift bereits während der Renderphase und arbeitet korrekt mit dem serverseitigen Rendering im Web zusammen.

Middleware und Routenschutz

Expo Router v6 führt serverseitige Middleware ein, die Anfragen abfängt, bevor sie den eigentlichen Routenkomponenten erreichen. Die Datei +middleware.ts im app-Verzeichnis dient als zentraler Punkt für Authentifizierungsprüfungen, Weiterleitungen und HTTP-Header-Manipulationen.

app/+middleware.tstypescript
import { type MiddlewareRequest } from 'expo-router/server'

export function middleware(request: MiddlewareRequest) {
  const { pathname } = request.nextUrl

  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
}

Auf nativen Plattformen steht die serverseitige Middleware nicht zur Verfügung. Der Routenschutz muss dort über clientseitige Wächter in den Layout-Komponenten umgesetzt werden. Das bewährteste Muster besteht darin, geschützte Routengruppen mit einer Authentifizierungsprüfung zu versehen:

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

Dieses Muster stellt sicher, dass kein geschützter Bildschirm gerendert wird, solange die Authentifizierung nicht abgeschlossen ist. Die Redirect-Komponente von Expo Router leitet den Benutzer um, bevor der geschützte Inhalt in den Komponentenbaum eingehängt wird.

Plattformspezifische Besonderheiten

Die Server-Middleware ist ausschließlich im Web mit serverseitigem Rendering verfügbar. Auf nativen Plattformen müssen clientseitige Wächter in den Layout-Komponenten implementiert werden. Nur die Kombination beider Strategien gewährleistet einen lückenlosen Routenschutz über alle Plattformen hinweg.

Fazit

Expo Router v6 überführt die Konventionen moderner Web-Frameworks in die React-Native-Welt und schafft ein einheitliches Navigationsmodell für mobile und Web-Plattformen. Die wichtigsten Erkenntnisse im Überblick:

  • Expo Router ersetzt die manuelle Navigationskonfiguration durch dateibasierte Konventionen -- das Anlegen einer Datei im app-Verzeichnis genügt, um eine neue Route zu erzeugen
  • _layout.tsx-Dateien definieren Navigationshierarchien mit Stacks, Tabs und Drawern, ohne dass eine zentrale Konfigurationsdatei erforderlich ist
  • Dynamische Routen mit [param].tsx und Catch-All-Routen mit [...slug].tsx ermöglichen parametrisierte Navigation mit vollständiger TypeScript-Unterstützung
  • Typed Routes erkennen ungültige Navigationslinks bereits zur Kompilierzeit, sobald die entsprechende Option in app.json aktiviert ist
  • Modale Bildschirme, programmatische Navigation über router.push/replace/back und Server-Middleware runden das Routing-Werkzeugset ab
  • Routengruppen in runden Klammern strukturieren den Code, ohne die URL-Hierarchie zu beeinflussen, und bewahren die Übersichtlichkeit auch in wachsenden Projekten
  • Clientseitige Wächter in Layouts sichern native Routen ab, Server-Middleware übernimmt den Schutz im Web -- die Kombination beider Ansätze garantiert plattformübergreifende Sicherheit

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Tags

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

Teilen

Verwandte Artikel