Frontendmigration ohne Ausfallzeit
Das Frontend neu entwickeln. 170 Standorte nutzen es weiterhin. Keine Ausfallzeit.
Eine laufende Migration von CRA zu Next.js mit einer V2-Shell, einer iframe-Brücke, gemeinsamer Authentifizierung, synchronisiertem Routing und schrittweiser Einführung.
5 Min. LesezeitVon Abdullah Raheel
Das Produkt durfte nicht pausieren
CRA V1 bediente weiterhin 170 Standorte und etwa 765 gleichzeitige Nutzer. Wir konnten die Funktionsentwicklung nicht einfrieren, isoliert einen Ersatz bauen und alle Kunden gleichzeitig umstellen. Die Migration musste innerhalb des laufenden Produkts stattfinden.
Wir nutzten das Strangler-Fig-Muster: das neue System um das alte legen, jeweils eine produktive Zuständigkeit verschieben und die Kompatibilitätsschicht löschen, sobald nichts mehr von ihr abhängt.
Ein Strangler-Muster statt eines zweiten Starts
Next.js V2 wurde zur Anwendungsshell. Es übernahm Navbar, Topbar, Authentifizierung und die globale Anwendungsoberfläche. Noch nicht migrierte Routen liefen weiterhin in einem einzelnen CRA-iframe, sodass Kunden ein Produkt sahen, während zwei Frontendlaufzeiten aktiv waren.
Der iframe lud stets eine stabile Dashboard-Basis-URL. Er blieb bei Änderungen der übergeordneten Route eingebunden; ein erneutes Einbinden hätte den alten Zustand zurückgesetzt und die Authentifizierung wiederholt. V2 sendete die erste Route erst im onLoad-Callback des iframe, nachdem V1 seinen Nachrichten-Listener installiert hatte, und spätere Änderungen nur dann, wenn sich die zugeordnete Route tatsächlich geändert hatte.
iframe.contentWindow?.postMessage(
{
action: 'NAVIGATE',
route: iframeRoute,
location_id: locationId,
migratedLocationIds: [...MIGRATED_LOCATION_IDS]
},
dashboardUrl
)Zwei Frontends wie eines funktionieren lassen
Wir legten beide Nachrichtenrichtungen ausdrücklich fest. V2 sendete Navigation, SSO-Token, Nutzerdaten und Standortdaten. V1 gab Routenänderungen, Nutzerdatenanforderungen, Anforderungen zur Tokenaktualisierung und Click2Call-Aktionen zurück.
Jeder Empfänger lehnte unerwartete Origins ab, und jeder Sender adressierte die konfigurierte Anwendungs-Origin statt eines Platzhalters. Origin-Prüfungen waren die erste Grenze, aber keine vollständige Nutzdatenvalidierung. Nachrichten mit Token benötigten außerdem bekannte Nachrichtentypen und validierte Felder.
if (event.origin !== dashboardUrl) return
if (event.data?.source !== 'dispo-iframe') return
if (event.data?.type !== 'ROUTE_CHANGE') return
const route = mapV1RouteToV2(event.data.route)
if (route) router.replace(route)Eine Anmeldung musste beide Anwendungen authentifizieren
Der Nutzer authentifizierte sich einmal in V2. Nach dem Laden forderte V1 den aktuellen Nutzerkontext an; V2 gab das SSO-Token sowie Nutzer- und Standortdaten zurück. V1 speicherte das Token für seinen vorhandenen API-Client und forderte nach Ablauf über dieselbe Brücke Ersatz an.
Während der Diagnose eines Nebenläufigkeitsproblems beim Start des iframe wurde ein vorausschauender Authentifizierungs-Hook eingeführt. Er versuchte, die Authentifizierung zu senden, bevor V1 sie anforderte, fügte damit aber einen weiteren zeitabhängigen Weg hinzu und reagierte zu früh. Eine spätere Änderung entfernte diesen Hook und behielt die reaktive Anfrage-Antwort-Authentifizierung bei. Damit gab es nur einen eindeutigen Verantwortlichen für den Tokenzeitpunkt.
Adressleiste und Browserverlauf müssen den tatsächlichen Zustand zeigen
V2 ordnete seine Routen V1-Pfaden wie /dashboard, /buyer-genie, /deals und /my-buyers zu. Die Rückwärtszuordnung verarbeitete sowohl genaue Routen als auch verschachtelte Präfixe. Begann die Navigation in V1, nutzte V2 router.replace, sodass sich Adressleiste und aktive Navigation änderten, ohne für eine Nutzeraktion einen zweiten Verlaufseintrag hinzuzufügen.
Damit blieben Aktualisieren, Deep Links und die Browsernavigation vorwärts und zurück schlüssig. Eine Browser-Zurück-Aktion änderte die übergeordnete Route, diese sendete die zugeordnete Kindroute, und der eingebundene iframe navigierte, ohne die Shell neu zu laden.
Kunden und Funktionen in kontrollierten Schritten umstellen
MIGRATED_LOCATION_IDS war der Schalter für die Einführung. Wir begannen mit internen und Staging-Standorten und erweiterten anschließend auf acht Kundenstandorte. Alle Standorte außerhalb der Liste blieben auf dem bestehenden V1-Weg.
- Nativ in V2: Navbar, Topbar, Settings, Investor Intake, SSO und Twilio Dialer.
- Zum dokumentierten Zwischenstand noch in V1: Home, Genius Mode, Deals und Buyers.
- Migrationseinheit: eine Funktion und ausgewählte Kundenstandorte, kein separater Produktstart.
Der Austausch blieb während der Einführung unsichtbar
Die Kompatibilitätsschicht wurde schließlich entfernt, statt zu dauerhafter Architektur zu werden. Beim endgültigen Wechsel wurden das iframe-System und die alten generierten API-Clients gelöscht, verbleibende Clients unter V2 verschoben und der native Anmeldeweg ergänzt. Anschließend wurden Routen, Tests, gemeinsam genutzter Dialercode, Authentifizierungs-Guards und die Komponentenstruktur bereinigt.
Eine Zeit lang pflegten wir zwei Router, zwei Sitzungsmodelle, fensterübergreifende Ereignisse, Routenzuordnungen und Race Conditions beim Start. Diese Kosten waren nur vertretbar, weil uns jede migrierte Route dem Löschen der Brücke näherbrachte.

