Navigation-State-Persistenz: den App-Neustart überleben
AI generated
RN
native
React Native / Navigation
Navigation-State-Persistenz: den App-Neustart überleben
Wie der Navigationszustand wiederhergestellt wird und wo bewusste Grenzen nötig sind

Wird eine React-Native-App per Force-Quit beendet und danach erneut geöffnet, geht standardmäßig der gesamte Navigationszustand verloren, weil sowohl React Navigation als auch Expo Router den aktuellen Screen und die Stack-Historie ausschließlich im Arbeitsspeicher halten. Für viele Apps ist das ein spürbarer Bruch im Nutzungserlebnis, wenn eine tief verschachtelte Ansicht nach jedem Neustart wieder beim Start-Screen beginnt. Dieser Artikel zeigt, wie sich der Navigationszustand gezielt persistieren lässt, welche Konfiguration dafür in React Navigation und Expo Router nötig ist, und wo diese Wiederherstellung bewusst unterbleiben sollte, etwa nach einem abgelaufenen Login.

11 Min. Lesezeit Navigation State Persistenz AsyncStorage

1. Warum der Navigationszustand standardmäßig verloren geht

Sowohl React Navigation als auch Expo Router verwalten den aktuellen Navigationszustand, also welcher Screen aktiv ist und welche Stack-Historie dahinter liegt, als reines JavaScript-Objekt im Arbeitsspeicher der laufenden App-Instanz. Wird der native Prozess beendet, sei es durch einen manuellen Force-Quit, durch das Betriebssystem im Rahmen des Speichermanagements oder durch einen Absturz, verschwindet dieses Objekt vollständig zusammen mit dem restlichen Laufzeitzustand der App. Beim nächsten Start wird die Navigation vollständig neu initialisiert und beginnt beim konfigurierten Startbildschirm, unabhängig davon, wo sich die Nutzerin zuvor befand.

Dieses Verhalten unterscheidet sich fundamental von nativen iOS- und Android-Apps, bei denen das Betriebssystem den UI-Zustand einzelner Ansichten in bestimmten Fällen über einen Prozessneustart hinweg wiederherstellen kann. React Native bietet diese Wiederherstellung nicht automatisch an, weil der gesamte Navigationsbaum als flexible, dynamisch erzeugte Datenstruktur im JavaScript-Bereich existiert und keine direkte Entsprechung im nativen View-Controller- beziehungsweise Activity-Zustand hat, den das Betriebssystem selbstständig sichern könnte.

2. Die State-Persistence-API von React Navigation

React Navigation stellt für genau dieses Problem eine eingebaute Persistence-API bereit, die über die Props onStateChange und initialState am NavigationContainer angebunden wird. Bei jeder Zustandsänderung, also bei jeder Navigation, meldet onStateChange den vollständigen neuen Zustand als serialisierbares Objekt, das anschließend in einem persistenten Speicher wie AsyncStorage abgelegt werden kann. Beim nächsten App-Start liest die Anwendung diesen gespeicherten Zustand vor dem ersten Rendern des NavigationContainer aus und übergibt ihn über initialState, wodurch React Navigation exakt an der zuvor gespeicherten Stelle weiterarbeitet, statt bei der konfigurierten Startroute zu beginnen.

Weil das Auslesen aus AsyncStorage asynchron erfolgt, muss der eigentliche NavigationContainer so lange zurückgehalten werden, bis der gespeicherte Zustand tatsächlich geladen wurde, üblicherweise über einen einfachen isReady-Flag im Komponentenzustand. Ohne diese Wartezeit würde der Container bereits mit dem Standardzustand initialisiert und der später eintreffende gespeicherte Zustand hätte keine Wirkung mehr, weil initialState ausschließlich beim allerersten Rendern ausgewertet wird.


import { useState, useCallback } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { NavigationContainer, InitialState } from '@react-navigation/native';

const PERSISTENCE_KEY = 'NAVIGATION_STATE_V3';

export default function AppNavigation() {
  const [isReady, setIsReady] = useState(false);
  const [initialState, setInitialState] = useState<InitialState>();

  const restoreState = useCallback(async () => {
    const savedStateString = await AsyncStorage.getItem(PERSISTENCE_KEY);
    if (savedStateString) {
      setInitialState(JSON.parse(savedStateString));
    }
    setIsReady(true);
  }, []);

  if (!isReady) {
    return null;
  }

  return (
    <NavigationContainer
      initialState={initialState}
      onStateChange={(state) =>
        AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state))
      }
    >
      {/* Navigator hier */}
    </NavigationContainer>
  );
}

3. Konkrete Implementierung Schritt für Schritt

In der Praxis lohnt es sich, das Laden des gespeicherten Zustands mit dem generellen App-Startvorgang zu verbinden, etwa mit dem Ausblenden eines Splash-Screens über expo-splash-screen, sodass die Nutzerin während des kurzen Ladevorgangs keinen leeren Bildschirm sieht. Der restoreState-Aufruf sollte dabei in einem try-catch-Block abgesichert werden, weil ein beschädigter oder aus einer inkompatiblen App-Version stammender gespeicherter Zustand sonst zu einem unbehandelten Fehler führen kann, der den App-Start blockiert.

Zusätzlich empfiehlt es sich, das Schreiben in AsyncStorage zu drosseln, statt bei jeder einzelnen Navigation sofort zu schreiben, etwa über ein einfaches Debouncing mit 300 bis 500 Millisekunden Verzögerung, weil sehr häufige, schnell aufeinanderfolgende Navigationen sonst unnötig viele Schreibvorgänge auf den persistenten Speicher auslösen und dadurch spürbar Leistung kosten können, insbesondere auf älteren Android-Geräten mit langsamerem internen Speicher.

4. Der Ansatz von Expo Router: URL statt Zustandsobjekt

Expo Router verfolgt einen konzeptionell anderen Ansatz als die klassische React-Navigation-Persistence-API, weil jede Route dort ohnehin bereits einer URL entspricht und der Navigationszustand sich zu einem großen Teil direkt aus dem aktuellen Pfad ableiten lässt. Statt eines komplexen, verschachtelten Zustandsobjekts genügt es in vielen Fällen, lediglich den zuletzt besuchten Pfad zu speichern und die App beim Neustart über router.replace direkt auf diesen Pfad zu navigieren, wodurch Expo Router die passende Stack-Historie größtenteils selbst rekonstruiert.

Für Fälle, in denen auch die vollständige Stack-Historie erhalten bleiben soll, etwa mehrere aufeinander aufgebaute Push-Schritte innerhalb eines Tabs, lässt sich weiterhin dieselbe onStateChange- und initialState-Mechanik von React Navigation nutzen, da Expo Router intern auf React Navigation aufbaut und denselben NavigationContainer verwendet. In der Praxis reicht für die meisten Apps jedoch die einfachere, URL-basierte Wiederherstellung des letzten Pfades völlig aus und vermeidet gleichzeitig die Komplexität einer vollständigen Zustandsserialisierung.

5. Versionierung des gespeicherten Zustands nicht vergessen

Ein gespeicherter Navigationszustand referenziert konkrete Routennamen und Screen-Parameter, die sich mit jedem App-Update ändern können, etwa wenn ein Screen umbenannt, ein Parameter umstrukturiert oder eine ganze Route entfernt wird. Wird ein solcher veralteter Zustand nach einem Update unverändert über initialState eingespielt, kann React Navigation versuchen, eine nicht mehr existierende Route zu rendern, was im besten Fall zu einem Absturz und im schlechteren Fall zu einem stillen, verwirrenden Fehlzustand führt.

Die zuverlässigste Absicherung dagegen ist ein Versionssuffix im AsyncStorage-Schlüssel selbst, wie im obigen Beispiel mit NAVIGATION_STATE_V3 gezeigt, das bei jeder strukturellen Änderung an der Navigationsstruktur bewusst hochgezählt wird. Ändert sich der Schlüssel, findet die App beim nächsten Start keinen gespeicherten Zustand mehr unter dem neuen Namen, verwirft den veralteten Eintrag stillschweigend und startet stattdessen sauber bei der konfigurierten Startroute, ohne dass ein Abgleich der Struktur programmiert werden müsste.

6. Wann der Zustand bewusst nicht wiederhergestellt werden sollte

Nicht jede Situation profitiert von einer Wiederherstellung des Navigationszustands, und ein Login-Screen ist das klarste Beispiel dafür: Ist die gespeicherte Session beim Neustart abgelaufen, wäre es fehlerhaft, die Nutzerin zunächst auf einen tief verschachtelten, eigentlich geschützten Screen zurückzuführen, nur um sie von dort sofort wieder zum Login umzuleiten. Ein Auth-Guard, der vor jeder Zustandswiederherstellung zuerst den aktuellen Session-Status prüft und bei fehlender Authentifizierung den gespeicherten Zustand vollständig verwirft, verhindert dieses kurze, aber verwirrende Aufblitzen geschützter Inhalte.

Ähnlich problematisch sind einmalige oder zeitkritische Flows wie ein Zahlungsvorgang, ein mehrstufiges Onboarding oder ein Bestätigungsdialog nach einer bereits abgeschlossenen Aktion, bei denen eine Wiederherstellung mitten im Prozess zu inkonsistenten oder doppelten Aktionen führen könnte. Für solche Screens empfiehlt es sich, sie explizit von der Persistenz auszuschließen, etwa indem der Speicherzustand vor dem Betreten dieser Screens bewusst nicht aktualisiert oder beim Verlassen des Flows aktiv gelöscht wird.

7. Selektive Persistenz: einzelne Screens gezielt ausschließen

Statt den gesamten Navigationszustand pauschal zu speichern, lässt sich der onStateChange-Handler so erweitern, dass er den empfangenen Zustand vor dem Schreiben inspiziert und bestimmte Routennamen gezielt herausfiltert oder durch eine sichere Fallback-Route ersetzt. Enthält der aktuelle Zustand beispielsweise eine Route namens Checkout oder PaymentConfirmation, wird statt des tatsächlichen Zustands ein reduzierter Zustand mit der übergeordneten, unkritischen Route gespeichert, sodass ein Neustart während eines Zahlungsvorgangs nicht versucht, genau diesen Vorgang unvollständig fortzusetzen.

Dieselbe Filterlogik eignet sich auch für Modal-Screens mit kurzer Lebensdauer, etwa einen einmaligen Berechtigungs-Dialog oder eine Bewertungsaufforderung, die nach einem Neustart keinen Sinn mehr ergeben, weil ihr ursprünglicher Auslöser nicht mehr im aktuellen Kontext existiert. Eine klare, dokumentierte Liste der von der Persistenz ausgeschlossenen Routen im Code selbst hilft dem gesamten Team, bei neuen Screens bewusst zu entscheiden, ob sie zur wiederherstellbaren oder zur bewusst flüchtigen Kategorie gehören.

8. Kombination mit Deep-Linking-Priorität beim Kaltstart

Wird eine App nicht einfach neu geöffnet, sondern über einen Deep Link gestartet, etwa aus einer Push-Benachrichtigung oder einem geteilten Produktlink heraus, sollte dieser Deep Link grundsätzlich Vorrang vor dem gespeicherten Navigationszustand haben, weil er die aktuellere, bewusste Nutzerabsicht ausdrückt. Die praktische Lösung besteht darin, beim App-Start zuerst zu prüfen, ob eine Linking-URL vorhanden ist, und den gespeicherten Zustand nur dann über initialState einzuspielen, wenn kein solcher Link vorliegt.

Diese Priorisierung verhindert eine verwirrende Situation, in der ein Nutzer auf eine Benachrichtigung tippt, aber wegen eines wiederhergestellten alten Navigationszustands auf einem völlig anderen Screen landet als dem, den die Benachrichtigung eigentlich adressiert hat. In der Praxis lässt sich diese Prüfung sauber in derselben restoreState-Funktion unterbringen, die auch den AsyncStorage-Zustand lädt, indem Linking.getInitialURL vor dem eigentlichen State-Restore ausgewertet wird.

9. Testing-Strategie für Force-Quit-Szenarien

Navigation-State-Persistenz lässt sich kaum sinnvoll durch klassische Komponententests abdecken, weil der eigentliche Effekt erst über einen echten Prozessneustart sichtbar wird, weshalb manuelles Testen auf physischen Geräten und Simulatoren ein fester Bestandteil des Test-Ablaufs bleiben sollte. Ein wiederholbarer manueller Testfall navigiert bewusst mehrere Ebenen tief in die App hinein, beendet den Prozess vollständig über das App-Umschalter-Menü und öffnet die App danach erneut, um zu prüfen, ob exakt derselbe Screen mit denselben Parametern wieder erscheint.

Zusätzlich sollte gezielt der Fall eines abgelaufenen Logins während eines Force-Quit getestet werden, indem die Session serverseitig oder über einen manuell manipulierten Token vorab ungültig gemacht wird, um sicherzustellen, dass der Auth-Guard den gespeicherten Zustand tatsächlich verwirft, statt einen geschützten Screen kurz aufblitzen zu lassen. Für automatisierte End-to-End-Tests mit Detox oder Maestro lässt sich ein App-Neustart-Schritt zusätzlich in die Testsuite aufnehmen, um zumindest den grundsätzlichen Wiederherstellungsmechanismus regelmäßig gegen Regressionen abzusichern.

Ansatz Speicherort Granularität Typischer Einsatz
React Navigation Persistence API AsyncStorage Vollständiger, verschachtelter Zustand Klassische Stack-/Tab-Apps mit React Navigation
Expo Router URL-Persistenz AsyncStorage (nur Pfad) Letzter besuchter Pfad Dateibasiertes Routing, einfache Wiederherstellung
Selektive Filterung AsyncStorage mit Vorverarbeitung Bestimmte Routen ausgeschlossen Checkout, Zahlungs- und Onboarding-Flows
Versionierter Schlüssel AsyncStorage mit Schlüsselsuffix Ganzer gespeicherter Zustand Absicherung gegen strukturelle App-Updates
Deep-Link-Priorität Kein zusätzlicher Speicher Einzelner Zielpfad Push-Benachrichtigungen, geteilte Links

Mironsoft

React-Native-App-Entwicklung und Magento-Anbindung

Eine mobile App zum Magento-Shop, die wirklich rund läuft?

Wir entwickeln React-Native-Apps, die sauber an die Magento REST- oder GraphQL-API angebunden sind, von der ersten Codezeile bis zur Veröffentlichung im App Store und bei Google Play.

App-Konzeption

Architektur und Feature-Umfang einer Magento-angebundenen App gemeinsam planen.

Magento-API-Integration

Produktkatalog, Warenkorb und Checkout sauber an die Shop-API anbinden.

Store-Veröffentlichung

App Store- und Google-Play-Freigabeprozess ohne Stolperfallen begleiten.

10. Zusammenfassung

Navigation-State-Persistenz: Das Wichtigste auf einen Blick

Grundmechanismus

onStateChange schreibt den Navigationszustand, initialState liest ihn beim nächsten Start vor dem ersten Rendern wieder ein.

Expo Router

Nutzt statt eines komplexen Zustandsobjekts meist nur den zuletzt besuchten Pfad und rekonstruiert die Historie daraus.

Grenzen

Login-Screens, Zahlungs- und Onboarding-Flows sollten bewusst von der Wiederherstellung ausgeschlossen werden.

Priorität

Ein Deep Link beim Kaltstart sollte immer Vorrang vor einem wiederhergestellten alten Navigationszustand haben.

11. FAQ: Navigation-State-Persistenz: Das Wichtigste auf einen Blick

1Warum geht der Navigationszustand bei einem Force-Quit standardmäßig verloren?
React Navigation und Expo Router halten den aktuellen Screen und die Stack-Historie ausschließlich als JavaScript-Objekt im Arbeitsspeicher der laufenden App-Instanz. Wird der native Prozess beendet, verschwindet dieses Objekt vollständig und die Navigation startet beim nächsten Öffnen neu bei der konfigurierten Startroute.
2Welche Props ermöglichen Navigation-State-Persistenz in React Navigation?
Die Props onStateChange und initialState am NavigationContainer. onStateChange meldet bei jeder Navigation den neuen Zustand als serialisierbares Objekt, initialState übernimmt beim nächsten Start einen zuvor gespeicherten Zustand, um exakt an dieser Stelle weiterzuarbeiten.
3Warum muss der NavigationContainer beim Start zurückgehalten werden?
Weil das Auslesen aus AsyncStorage asynchron erfolgt und initialState nur beim allerersten Rendern ausgewertet wird. Ohne eine kurze Wartezeit über einen isReady-Flag würde der Container bereits mit dem Standardzustand starten, bevor der gespeicherte Zustand überhaupt geladen ist.
4Wie unterscheidet sich der Ansatz von Expo Router von der klassischen React-Navigation-Persistenz?
Weil jede Route in Expo Router ohnehin einer URL entspricht, genügt in den meisten Fällen das Speichern des zuletzt besuchten Pfades, statt eines vollständigen, verschachtelten Zustandsobjekts. Die App navigiert beim Neustart einfach über router.replace zu diesem Pfad und rekonstruiert die Historie größtenteils selbst.
5Warum sollte der gespeicherte Zustand versioniert werden?
Weil sich Routennamen und Screen-Parameter mit App-Updates ändern können. Ein Versionssuffix im AsyncStorage-Schlüssel sorgt dafür, dass ein veralteter, strukturell nicht mehr passender Zustand nach einem Update stillschweigend verworfen wird, statt zu einem Absturz oder Fehlzustand zu führen.
6Wann sollte der Navigationszustand bewusst nicht wiederhergestellt werden?
Vor allem bei abgelaufenen Login-Sessions, bei denen ein Auth-Guard den gespeicherten Zustand verwerfen sollte, bevor geschützte Inhalte kurz sichtbar werden. Auch einmalige oder zeitkritische Flows wie Zahlungsvorgänge oder mehrstufiges Onboarding profitieren nicht von einer Wiederherstellung.
7Wie lassen sich einzelne Screens gezielt von der Persistenz ausschließen?
Der onStateChange-Handler kann den empfangenen Zustand vor dem Schreiben inspizieren, bestimmte Routennamen wie Checkout oder PaymentConfirmation herausfiltern und stattdessen eine übergeordnete, unkritische Route speichern, statt des tatsächlichen, kritischen Zustands.
8Warum sollte ein Deep Link Vorrang vor dem gespeicherten Navigationszustand haben?
Weil ein Deep Link, etwa aus einer Push-Benachrichtigung, die aktuellere, bewusste Nutzerabsicht ausdrückt. Ohne diese Priorisierung könnte ein Nutzer auf eine Benachrichtigung tippen und trotzdem auf einem völlig anderen, wiederhergestellten Screen landen.
9Wie testet man Navigation-State-Persistenz zuverlässig?
Vor allem manuell, weil der Effekt erst über einen echten Prozessneustart sichtbar wird: mehrere Ebenen tief navigieren, den Prozess über das App-Umschalter-Menü vollständig beenden und erneut öffnen, um zu prüfen, ob derselbe Screen mit denselben Parametern wieder erscheint.
10Sollte das Schreiben in AsyncStorage bei jeder Navigation sofort erfolgen?
Nein, ein einfaches Debouncing mit 300 bis 500 Millisekunden Verzögerung reduziert die Zahl der Schreibvorgänge bei schnell aufeinanderfolgenden Navigationen deutlich und vermeidet spürbare Leistungseinbußen, insbesondere auf älteren Android-Geräten.