Naar inhoud

Frontendmigratie zonder downtime

De frontend herschrijven. 170 locaties gebruiken hem nog. Geen downtime.

Een live CRA-naar-Next.js-migratie met een V2-shell, iframebrug, gedeelde authenticatie, gesynchroniseerde routering en gefaseerde uitrol.

5 min. leestijdDoor

Het product kon niet worden stilgelegd

CRA V1 bediende nog 170 locaties en ongeveer 765 gelijktijdige gebruikers. We konden het functiewerk niet stilleggen, afzonderlijk een vervanging bouwen en alle klanten tegelijk overzetten. De migratie moest binnen het live product plaatsvinden.

We gebruikten het Strangler Fig-patroon: plaats het nieuwe systeem rond het oude, verplaats één productieverantwoordelijkheid per keer en verwijder de compatibiliteitslaag wanneer niets er meer van afhankelijk is.

Gebruik een strangler, geen tweede lancering

Next.js V2 werd de toepassingsshell. Deze beheerde de navigatiebalk, bovenbalk, authenticatie en algemene interface-elementen. Nog niet gemigreerde routes bleven in één CRA-iframe draaien, zodat klanten één product zagen terwijl twee frontendruntimes actief waren.

Het iframe laadde altijd een stabiele basis-URL voor het dashboard. Het bleef bij wijzigingen aan de bovenliggende route gemount; opnieuw mounten zou de verouderde status hebben gewist en de authenticatie hebben herhaald. V2 stuurde de beginroute alleen vanuit de onLoad-callback van het iframe, nadat V1 de berichtenlistener had geïnstalleerd, en stuurde latere wijzigingen alleen wanneer de toegewezen route werkelijk veranderde.

iframe.contentWindow?.postMessage(
  {
    action: 'NAVIGATE',
    route: iframeRoute,
    location_id: locationId,
    migratedLocationIds: [...MIGRATED_LOCATION_IDS]
  },
  dashboardUrl
)

Laat twee frontends zich als één gedragen

We definieerden beide berichtrichtingen expliciet. V2 stuurde navigatie, SSO-tokens, gebruikersgegevens en locatiegegevens. V1 stuurde routewijzigingen, aanvragen om gebruikersgegevens, aanvragen om tokenvernieuwing en Click2Call-acties terug.

Elke ontvanger weigerde onverwachte origins en elke afzender richtte zich op de geconfigureerde oorsprong van de toepassing in plaats van een jokerteken te gebruiken. Origincontroles vormden de eerste grens, niet de volledige payloadvalidatie. Berichten met tokens vereisten ook bekende berichttypen en gevalideerde velden.

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)

Met één aanmelding moesten beide toepassingen worden geauthenticeerd

De gebruiker authenticeerde zich één keer in V2. Toen V1 werd geladen, vroeg het de huidige gebruikerscontext aan; V2 stuurde het SSO-token met gebruikers- en locatiegegevens terug. V1 sloeg het token op voor de bestaande API-client en vroeg na het verlopen via dezelfde brug een vervangend token aan.

Tijdens het onderzoeken van een racecondition bij het opstarten van het iframe werd een proactieve authenticatiehook toegevoegd. Die probeerde authenticatie te pushen voordat V1 erom vroeg, maar voegde nog een tijdpad toe en werd overactief. Een latere wijziging verwijderde die hook en behield reactieve authenticatie met aanvraag en antwoord, zodat één duidelijke eigenaar over de tokentiming besliste.

Zorg dat de adresbalk en browsergeschiedenis blijven kloppen

V2 koppelde de eigen routes aan V1-paden zoals /dashboard, /buyer-genie, /deals en /my-buyers. De omgekeerde koppeling verwerkte zowel exacte routes als geneste voorvoegsels. Wanneer navigatie binnen V1 begon, gebruikte V2 router.replace, zodat de adresbalk en actieve navigatie veranderden zonder voor één gebruikersactie een tweede geschiedenisitem toe te voegen.

Daardoor bleven vernieuwen, deeplinks en terug- en vooruitnavigatie van de browser samenhangend. Een browseractie Terug wijzigde de bovenliggende route, de bovenliggende toepassing stuurde de gekoppelde onderliggende route en het gemounte iframe navigeerde zonder de shell opnieuw te laden.

Verplaats klanten en functies in beheersbare delen

MIGRATED_LOCATION_IDS was de uitrolschakelaar. We begonnen met interne en staginglocaties en breidden daarna uit naar acht klantlocaties. Iedereen buiten de lijst bleef op het bestaande V1-pad.

  • Native V2: Navbar, Topbar, Settings, Investor Intake, SSO en Twilio Dialer.
  • Op het vastgelegde tussenpunt nog in V1: Home, Genius Mode, Deals en Buyers.
  • Migratie-eenheid: één functie en geselecteerde klantlocaties, niet één afzonderlijke productlancering.

De vervanging bleef tijdens de uitrol onzichtbaar

De compatibiliteitslaag werd uiteindelijk verwijderd in plaats van permanente architectuur te worden. Bij de omschakeling werden het iframesysteem en de verouderde gegenereerde API-clients verwijderd, overgebleven clients onder V2 geplaatst en het native aanmeldpad toegevoegd. Daarna werden routes, tests, gedeelde dialercode, authenticatieguards en de componentenstructuur opgeschoond.

Een tijdlang onderhielden we twee routers, twee sessiemodellen, gebeurtenissen tussen vensters, routekoppelingen en raceconditions bij het opstarten. Die kosten waren alleen aanvaardbaar omdat elke gemigreerde route ons dichter bij het verwijderen van de brug bracht.