Expo Router: dateibasiertes Routing in der Praxis
AI generated
RN
native
React Native · Expo Router · Navigation · Mobile Commerce
Expo Router
dateibasiertes Routing in der Praxis

Expo Router ersetzt manuell verdrahtete Navigatoren durch das app/-Verzeichnis als einzige Quelle der Wahrheit. Verschachtelte Layouts über _layout.tsx, dynamische Segmente wie [id].tsx, Route Groups mit runden Klammern und automatisch funktionierendes Deep Linking machen React-Native-Navigation vorhersagbar und typsicher.

17 Min. Lesezeit app/ · _layout.tsx · Typed Routes · Deep Linking Expo SDK 50+ · React Native 0.73+

1. Was Expo Router wirklich löst

Expo Router ist kein weiterer Navigator neben Stack und Tabs, sondern eine Konvention: die Ordnerstruktur unter app/ beschreibt direkt, welche Bildschirme eine App hat und wie sie zusammenhängen. Wer bisher mit React Navigation gearbeitet hat, kennt das Muster, dass jeder neue Screen an drei Stellen eingetragen werden muss: als Komponente, als Eintrag in der Navigator-Konfiguration und häufig auch in einer separaten Typdefinition für die Parameterliste. Expo Router reduziert das auf einen einzigen Schritt: eine Datei in app/ anlegen, und die Route existiert.

Das eigentliche Problem, das Expo Router adressiert, ist nicht Bequemlichkeit, sondern Konsistenz zwischen drei Dingen, die in klassischen React-Native-Apps oft auseinanderdriften: der sichtbaren Navigationsstruktur, der URL-Struktur für Deep Links und der Typdefinition der Navigationsparameter. Bei manuell verdrahteten Navigatoren müssen alle drei synchron gehalten werden, jede Änderung an einem Screen-Namen erfordert Anpassungen an mehreren Stellen. Mit Expo Router ist der Dateipfad gleichzeitig die Navigationsstruktur und die Basis für die URL, wodurch eine ganze Klasse von Synchronisationsfehlern gar nicht erst entsteht.

Ein weiterer Aspekt, der oft unterschätzt wird: Expo Router baut auf React Navigation auf, ersetzt es also nicht, sondern generiert die Navigator-Konfiguration zur Laufzeit aus der Dateistruktur. Wer die Interna von React Navigation kennt, kann sie weiterhin nutzen, etwa über unstable_settings oder direkten Zugriff auf den zugrunde liegenden Navigator. Der Unterschied liegt in der Erfahrung beim Hinzufügen neuer Screens, nicht in der Navigationslogik selbst.

2. Das app/-Verzeichnis: Dateien als Routen

Jede Datei unter app/ wird bei Expo Router automatisch zu einer Route. app/index.tsx ist die Startseite der App, app/settings.tsx ergibt die Route /settings, und ein Unterordner wie app/product/details.tsx ergibt /product/details. Diese Zuordnung ist keine zusätzliche Konfiguration, sondern das direkte Ergebnis der Dateisystemstruktur. Für Teams, die von Web-Frameworks mit ähnlicher Konvention kommen, ist dieses Verhalten sofort vertraut, für Teams aus der klassischen React-Native-Welt ist es meist der größte Umgewöhnungsschritt.

Wichtig ist, dass Expo Router zwischen normalen Routen-Dateien und speziellen Dateien unterscheidet, die mit einem Unterstrich beginnen, etwa _layout.tsx. Diese Sonderdateien erzeugen selbst keine eigene Route, sondern definieren, wie die Routen in ihrem Verzeichnis dargestellt werden. Eine Beispielstruktur für eine kleine Shop-App könnte so aussehen: app/_layout.tsx für das Root-Layout, app/(tabs)/index.tsx für den Startbildschirm, app/product/[id].tsx für die Produktdetailseite und app/cart.tsx für den Warenkorb. Jede dieser Dateien ist ein eigenständiges Modul, das nur geladen wird, wenn die entsprechende Route tatsächlich aufgerufen wird.

Diese Struktur hat einen direkten Effekt auf die Bundle-Größe: Weil jede Route ein eigenes Modul ist, kann Expo Router zusammen mit dem Metro-Bundler Code-Splitting auf Routen-Ebene vornehmen, insbesondere im Web-Export. Bei klassischen React-Navigation-Apps werden dagegen häufig alle Screen-Komponenten im initialen Navigator-Setup importiert, selbst wenn ein Nutzer nie zur entsprechenden Route navigiert. Für Mobile-Commerce-Apps mit vielen Produktkategorien und Sonderseiten ist dieser Unterschied bei der initialen Ladezeit spürbar.


# Create a new Expo project with the router template pre-installed
npx create-expo-app@latest my-shop-app --template tabs

cd my-shop-app

# The app/ directory is the single source of truth for navigation
find app -type f | sort
# app/_layout.tsx
# app/(tabs)/_layout.tsx
# app/(tabs)/index.tsx
# app/(tabs)/categories.tsx
# app/(tabs)/cart.tsx
# app/product/[id].tsx
# app/[...missing].tsx

# Start the dev server, deep links work immediately in Expo Go
npx expo start

3. _layout.tsx: verschachtelte Navigation

Die Datei _layout.tsx ist das Herzstück der Navigationshierarchie in Expo Router. Sie exportiert eine Komponente, die typischerweise Stack, Tabs oder Drawer aus dem Paket expo-router zurückgibt, und definiert damit, wie alle Kind-Routen im selben Verzeichnis präsentiert werden. Ein Root-Layout unter app/_layout.tsx umschließt die gesamte App, ein weiteres Layout unter app/(tabs)/_layout.tsx definiert nur die Tab-Leiste für die darin liegenden Routen. Diese Verschachtelung folgt exakt der Ordnerstruktur, ein Blick in das Dateisystem zeigt sofort die komplette Navigationshierarchie der App.

Der praktische Vorteil gegenüber manuell komponierten Navigatoren zeigt sich bei typischen Mobile-Commerce-Strukturen: eine Tab-Leiste für Start, Kategorien, Warenkorb und Konto, innerhalb der Kategorien-Tab aber ein Stack-Navigator für Kategorieliste, Produktliste und Produktdetail. Mit React Navigation wird das über einen Tab.Navigator gebaut, der als Screen-Komponenten einzelne Stack.Navigator-Instanzen enthält, alles in einer zentralen Datei. Mit Expo Router entsteht dieselbe Struktur automatisch: ein _layout.tsx mit Tabs im Ordner (tabs), und darin ein weiterer Unterordner mit eigenem _layout.tsx, das einen Stack definiert.


// app/_layout.tsx, root layout, wraps the entire app
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Screen name="(tabs)" />
      <Stack.Screen name="product/[id]" options={{ headerShown: true, title: 'Product' }} />
      <Stack.Screen name="[...missing]" options={{ title: 'Not found' }} />
    </Stack>
  );
}

4. Dynamische Segmente: [id].tsx und Parameter

Dynamische Routen sind bei Expo Router ebenfalls eine reine Dateinamenskonvention: eine Datei mit eckigen Klammern im Namen, etwa app/product/[id].tsx, matcht jeden beliebigen Wert an dieser Position im Pfad, also /product/123 genauso wie /product/abc-sneaker. Der eingeklammerte Teil des Dateinamens wird gleichzeitig zum Namen des Parameters, unter dem der Wert später über einen Hook abgerufen werden kann. Diese Eins-zu-eins-Beziehung zwischen Dateiname und Parametername ist bewusst so gestaltet, dass es keine zusätzliche Stelle gibt, an der Parameter und Route synchron gehalten werden müssen.

Mehrere dynamische Segmente lassen sich verschachteln, etwa app/store/[storeId]/product/[productId].tsx für eine Struktur mit mehreren Verkaufskanälen. Jedes Segment wird unabhängig aufgelöst, und beide Werte stehen im selben Objekt zur Verfügung, wenn die Seite die zugehörigen Parameter ausliest. Für eine Mobile-Commerce-App, die gegen eine Magento-Instanz über REST oder GraphQL spricht, ist das direkt nutzbar: die Produkt-ID aus dem Pfad wird unverändert als Parameter an den API-Aufruf gegen den Storefront-Endpunkt weitergereicht, ohne zusätzliche Zuordnungslogik zwischen Navigationsebene und Datenebene.

Ein Detail, das in der Praxis oft übersehen wird: dynamische Segmente sind standardmäßig immer Strings, auch wenn der Wert im Pfad wie eine Zahl aussieht. Wer eine numerische Produkt-ID an einen API-Client übergibt, der eine Zahl erwartet, muss explizit konvertieren. Dieses Verhalten ist konsistent mit der URL-basierten Natur von Expo Router, denn auch im Web sind Pfadsegmente grundsätzlich Text.


// app/product/[id].tsx, dynamic segment reads its value via useLocalSearchParams,
// useRouter navigates onward after a successful action
import { View, Text, Pressable } from 'react-native';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { useProduct } from '../../hooks/useProduct';
import { useCart } from '../../hooks/useCart';

export default function ProductDetailScreen() {
  // "id" matches the file name [id].tsx exactly
  const { id } = useLocalSearchParams<{ id: string }>();
  const router = useRouter();
  const { product, isLoading } = useProduct(id);
  const { addItem } = useCart();

  if (isLoading || !product) {
    return <Text>Loading product...</Text>;
  }

  const handleAddToCart = () => {
    addItem(product.id, 1);
    // Navigate to the cart route after the item was added
    router.push('/cart');
  };

  return (
    <View>
      <Text>{product.name}</Text>
      <Text>{product.price}</Text>
      <Pressable onPress={handleAddToCart}>
        <Text>Add to cart</Text>
      </Pressable>
    </View>
  );
}

5. Route Groups: (tabs) ohne URL-Segment

Route Groups sind eines der am wenigsten intuitiven, aber nützlichsten Features von Expo Router. Ein Ordner, dessen Name in runde Klammern gesetzt ist, etwa (tabs), wird zwar für die Navigationshierarchie und für Layout-Zwecke verwendet, taucht aber nicht als Segment in der resultierenden URL auf. app/(tabs)/index.tsx ergibt also die Route /, nicht /(tabs)/. Route Groups lösen damit ein Problem, das ohne dieses Feature kaum sauber lösbar wäre: eine gemeinsame Tab-Leiste für mehrere Top-Level-Routen, ohne dass diese Gruppierung die öffentlich sichtbare URL-Struktur verunreinigt.

In der Praxis nutzt man Route Groups auch, um mehrere unabhängige Root-Layouts zu bauen, zum Beispiel eine Gruppe (auth) für Login und Registrierung ohne Tab-Leiste, und eine Gruppe (tabs) für den eingeloggten Bereich mit Tab-Navigation. Beide Gruppen liegen direkt unter app/, jede mit eigenem _layout.tsx, und Expo Router entscheidet anhand des Authentifizierungsstatus, welche Gruppe aktuell gerendert wird. Diese Trennung ohne URL-Auswirkung ist mit klassischen React-Navigation-Setups zwar auch machbar, erfordert dort aber deutlich mehr manuelle Bedingungslogik im Root-Navigator.


// app/(tabs)/_layout.tsx, tab bar for the logged-in area,
// the "(tabs)" segment itself never appears in the URL
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';

export default function TabsLayout() {
  return (
    <Tabs screenOptions={{ tabBarActiveTintColor: '#4338ca' }}>
      <Tabs.Screen
        name="index"
        options={{ title: 'Home', tabBarIcon: (props) => <Ionicons name="home" {...props} /> }}
      />
      <Tabs.Screen
        name="categories"
        options={{ title: 'Categories', tabBarIcon: (props) => <Ionicons name="grid" {...props} /> }}
      />
      <Tabs.Screen
        name="cart"
        options={{ title: 'Cart', tabBarIcon: (props) => <Ionicons name="cart" {...props} /> }}
      />
    </Tabs>
  );
}

6. Catch-All-Routen: [...missing].tsx

Für Pfade, die zu keiner definierten Route passen, bietet Expo Router Catch-All-Routen an, gekennzeichnet durch drei Punkte innerhalb der eckigen Klammern, etwa app/[...missing].tsx. Diese Datei fängt jeden Pfad ab, der nicht von einer spezifischeren Route bedient wird, und ist damit die direkte Entsprechung einer 404-Seite im Web, übertragen auf eine native App. Ohne diese Route würde ein ungültiger Deep Link, etwa aus einer Push-Benachrichtigung mit veralteter Produkt-ID, zu einem undefinierten Zustand oder einem Absturz führen.

Der eingefangene Pfad steht innerhalb der Catch-All-Route ebenfalls als Parameter zur Verfügung, meist als Array der einzelnen Pfadsegmente. Das erlaubt es, in der Fehlerseite gezielt zu protokollieren, welcher Pfad nicht aufgelöst werden konnte, was bei der Fehlersuche in Produktions-Apps mit vielen extern verlinkten Deep Links erheblich hilft. Für eine Mobile-Commerce-App ist das besonders relevant, wenn Produkt-IDs sich ändern oder Kategorien umstrukturiert werden, während alte Links etwa aus E-Mail-Kampagnen oder Social-Media-Beiträgen weiterhin im Umlauf sind.

7. Typed Routes: Typsicherheit für Navigation

Typed Routes sind eine Funktion von Expo Router, die aus der vorhandenen Dateistruktur unter app/ automatisch TypeScript-Typen für alle gültigen Pfade generiert. Aktiviert wird das Feature über die Experiments-Einstellung in der app.json, danach erzeugt der Entwicklungsserver bei jedem Speichern einer neuen Route-Datei die passenden Typdefinitionen neu. Ein Aufruf von router.push mit einem nicht existierenden Pfad oder mit fehlenden Pflichtparametern wird dadurch bereits beim Kompilieren als Fehler markiert, nicht erst zur Laufzeit auf dem Gerät.

Dieser Unterschied zu manuell gepflegten Typdefinitionen ist in der Praxis erheblich. Bei React Navigation muss ein Entwickler eine RootStackParamList von Hand pflegen und bei jeder neuen Route synchron zur tatsächlichen Navigator-Konfiguration halten, eine Quelle, die in größeren Teams regelmäßig auseinanderläuft. Mit Typed Routes gibt es nur eine Quelle der Wahrheit, das Dateisystem selbst, aus der die Typen mechanisch abgeleitet werden. Ein Tippfehler im Pfad, etwa /produtc/123 statt /product/123, wird so noch während der Entwicklung sichtbar und nicht erst durch einen Bug-Report aus der Produktion.


{
  "expo": {
    "name": "my-shop-app",
    "scheme": "myapp",
    "experiments": {
      "typedRoutes": true
    },
    "plugins": [
      "expo-router"
    ]
  }
}

8. useRouter und useLocalSearchParams

Expo Router stellt für programmatische Navigation den Hook useRouter bereit, der Methoden wie push, replace, back und setParams anbietet. Anders als bei React Navigation, wo die Navigation-Prop üblicherweise als Parameter durch mehrere Komponentenebenen gereicht werden muss, kann useRouter direkt in jeder Komponente innerhalb einer Route aufgerufen werden, unabhängig davon, wie tief sie in der Komponentenhierarchie verschachtelt ist. Für das Auslesen von Parametern aus dem aktuellen Pfad, egal ob dynamisches Segment oder Query-String, dient der Hook useLocalSearchParams.

In der Praxis kombinieren sich beide Hooks häufig in derselben Komponente, wie im Codebeispiel aus Abschnitt 4 zu sehen: useLocalSearchParams liest die Produkt-ID aus dem Pfad, ein Datenladevorgang holt darauf aufbauend die Produktdetails, und useRouter navigiert nach einer erfolgreichen Aktion, etwa dem Hinzufügen zum Warenkorb, gezielt weiter zur Cart-Route. Weil beide Hooks direkt auf dem aktuellen Navigationszustand arbeiten, entfällt das manuelle Weiterreichen von Navigations- und Routen-Props, das in tief verschachtelten React-Navigation-Komponentenbäumen sonst schnell unübersichtlich wird.

Neben push bietet useRouter auch replace, das die aktuelle Route in der History ersetzt statt eine neue hinzuzufügen, nützlich etwa nach einem Login, damit der Zurück-Button nicht erneut zum Login-Screen führt. setParams erlaubt es, Query-Parameter der aktuellen Route zu ändern, ohne einen kompletten Navigationsvorgang auszulösen, was sich für Filter- und Sortierzustände auf Kategorieseiten eignet, ohne dass der Rest des Bildschirms neu gemountet wird.

9. Deep Linking und Migration von React Navigation

Deep Linking funktioniert bei Expo Router ohne zusätzliche Konfiguration, weil jede Route bereits einen eindeutigen Pfad besitzt. Bei klassischem React Navigation muss ein separates Linking-Konfigurationsobjekt gepflegt werden, das jeden Screen-Namen auf ein URL-Muster abbildet, inklusive verschachtelter Navigatoren, deren Pfade manuell zusammengesetzt werden müssen. Bei Expo Router ist dieses Mapping bereits durch die Dateistruktur gegeben, ein externer Link auf myapp://product/123 oder ein Universal Link auf https://shop.example.com/product/123 öffnet direkt die passende Route, inklusive korrekt aufgelöster Parameter.

Für Teams, die eine bestehende React-Navigation-App migrieren, empfiehlt sich ein schrittweises Vorgehen statt eines kompletten Rewrites: Zunächst wird expo-router installiert und ein minimales Root-Layout angelegt, das die vorhandenen Top-Level-Screens als Routen unter app/ abbildet, während die inneren Screen-Komponenten zunächst unverändert weiterverwendet werden. Anschließend werden Navigationsaufrufe schrittweise von navigation.navigate auf router.push umgestellt, Route für Route, Team für Team. Erst wenn alle Screens migriert sind, werden die manuell gepflegte Param-List und die Linking-Konfiguration entfernt.

Der folgende Vergleich fasst die wichtigsten Unterschiede zwischen manuell verdrahtetem React Navigation und dateibasiertem Expo Router zusammen, insbesondere für Teams, die eine Migrationsentscheidung treffen müssen.

Aspekt React Navigation (manuell verdrahtet) Expo Router (dateibasiert) Vorteil
Navigator-Setup Stack/Tab-Navigator manuell in zentraler Datei komponieren Datei in app/ anlegen, Route existiert automatisch Kein Boilerplate, Struktur = Navigation
Deep Linking Separates Linking-Config-Objekt pflegen Ergibt sich automatisch aus dem Dateipfad Keine manuelle Pfad-Zuordnung
Typisierte Parameter RootStackParamList von Hand synchron halten Typed Routes generieren Typen aus app/ Keine Sync-Fehler zwischen Code und Typen
Code-Splitting Alle Screens im initialen Navigator-Import Jede Route eigenes, lazy geladenes Modul Kleineres initiales Bundle
Verschachtelte Layouts Navigatoren ineinander manuell komponieren _layout.tsx spiegelt die Ordnerstruktur Hierarchie ist im Dateisystem sichtbar

Mironsoft

React-Native-Apps und Mobile-Commerce-Storefronts für Magento

Eine React-Native-App mit sauberer Navigation für euren Magento-Shop?

Wir bauen React-Native-Apps mit Expo Router und binden sie über REST- oder GraphQL-Schnittstellen an bestehende Magento-Instanzen an, von Produktdetailseiten mit dynamischen Routen bis zu Deep Links aus Push-Benachrichtigungen und Marketing-Kampagnen.

Code-Review

Bestehende Expo-Router- oder React-Navigation-Strukturen auf Wartbarkeit und Typsicherheit prüfen

App-Architektur

Verschachtelte Layouts, Route Groups und Typed Routes für skalierbare Mobile-Commerce-Apps aufsetzen

API-Integration

Magento REST- und GraphQL-Endpunkte direkt an dynamische Routen und Produktseiten anbinden

10. Zusammenfassung

Expo Router löst das grundlegende Problem manuell verdrahteter Navigation, indem es das Dateisystem selbst zur einzigen Quelle der Wahrheit macht. Das app/-Verzeichnis definiert die Routen, _layout.tsx-Dateien definieren verschachtelte Navigation, dynamische Segmente wie [id].tsx übernehmen Parameter direkt aus dem Pfad, und Route Groups mit runden Klammern erlauben Gruppierung ohne Auswirkung auf die URL. Catch-All-Routen fangen ungültige Pfade sauber ab, Typed Routes generieren Navigationstypen automatisch aus der Ordnerstruktur, und Deep Linking funktioniert ohne separate Konfiguration.

Für Teams, die von React Navigation migrieren, ist der schrittweise Weg über ein minimales Root-Layout der risikoärmste Ansatz, weil bestehende Screen-Komponenten zunächst unverändert weiterverwendet werden können. Der größte Effekt zeigt sich langfristig bei der Wartbarkeit: Weil Navigationsstruktur, URL-Struktur und Typdefinitionen aus derselben Quelle stammen, entsteht die Klasse von Synchronisationsfehlern, die manuell verdrahtete Navigatoren regelmäßig produzieren, mit Expo Router erst gar nicht.

Expo Router: dateibasiertes Routing, das Wichtigste auf einen Blick

app/-Verzeichnis

Jede Datei unter app/ wird automatisch zu einer Route. Keine zentrale Navigator-Konfiguration nötig, das Dateisystem ist die Navigationsstruktur.

Verschachtelte Layouts

_layout.tsx pro Ordnerebene definiert Stack, Tabs oder Drawer. Die Verschachtelung folgt exakt der Ordnerstruktur.

Dynamische Segmente & Typed Routes

[id].tsx liefert Parameter direkt aus dem Pfad, Typed Routes generieren dazu automatisch TypeScript-Typen aus app/.

Deep Linking & Migration

Deep Links funktionieren ohne separate Konfiguration. Migration von React Navigation gelingt schrittweise über ein minimales Root-Layout.

11. FAQ: Expo Router und dateibasiertes Routing

1Was ist Expo Router genau?
Ein dateibasiertes Routing-System für React Native und Expo, das auf React Navigation aufbaut. Die app/-Ordnerstruktur definiert automatisch die Navigation.
2Ersetzt es React Navigation komplett?
Nein, Expo Router generiert die Navigator-Konfiguration zur Laufzeit aus der Dateistruktur. React Navigation läuft weiterhin darunter.
3Wie erstelle ich eine dynamische Route?
Datei mit eckigen Klammern anlegen, z.B. app/product/[id].tsx. Der Wert steht danach über useLocalSearchParams als Parameter id zur Verfügung.
4Was bewirkt (tabs) als Ordnername?
Eine Route Group. Strukturiert Navigation und Layout, erscheint aber nicht als Segment in der URL.
5Wofür eine Catch-All-Route?
Fängt jeden nicht passenden Pfad ab, etwa bei ungültigen Deep Links. Entspricht einer 404-Seite, übertragen auf eine native App.
6Was sind Typed Routes?
Automatisch aus app/ generierte TypeScript-Typen für alle gültigen Pfade. Ungültige router.push-Aufrufe werden bereits beim Kompilieren erkannt.
7Wie navigiere ich programmatisch?
Über den Hook useRouter mit push, replace und back. Die Navigation-Prop muss dafür nicht durch Komponentenebenen gereicht werden.
8Funktioniert Deep Linking automatisch?
Ja, jede Route hat bereits einen eindeutigen Pfad. Keine separate Linking-Konfiguration nötig, Parameter werden automatisch aufgelöst.
9Wie migriere ich von React Navigation?
Schrittweise: minimales Root-Layout anlegen, Screens einbinden, Navigationsaufrufe nach und nach umstellen, alte Param-List erst am Ende entfernen.
10Verbessert es die Bundle-Größe?
Ja, jede Route ist ein eigenes Modul, Code-Splitting auf Routen-Ebene ist möglich, besonders im Web-Export.