Migración frontend sin tiempo de inactividad
Reescribir el frontend. 170 ubicaciones aún lo usan. Cero tiempo de inactividad.
Una migración en vivo de CRA a Next.js con un shell V2, un puente iframe, autenticación compartida, rutas sincronizadas y despliegue gradual.
5 min de lecturaPor Abdullah Raheel
El producto no podía detenerse
CRA V1 todavía atendía 170 ubicaciones y unos 765 usuarios simultáneos. No podíamos congelar el desarrollo de funciones, construir un sustituto de forma aislada y trasladar a todos los clientes a la vez. La migración debía ejecutarse dentro del producto activo.
Usamos el patrón Strangler Fig: colocar el sistema nuevo alrededor del antiguo, trasladar una responsabilidad de producción cada vez y eliminar la capa de compatibilidad cuando nada dependa de ella.
Usa el patrón Strangler Fig, no un segundo lanzamiento
Next.js V2 pasó a ser el contenedor principal de la aplicación. Se encargaba de la barra de navegación, la barra superior, la autenticación y el marco global de la interfaz. Las rutas aún no migradas continuaban dentro de un único iframe de CRA, por lo que los clientes veían un solo producto mientras estaban activos dos entornos frontend.
El iframe siempre cargaba una URL base estable del panel. Permanecía montado durante los cambios de la ruta principal; volver a montarlo habría borrado el estado heredado y repetido la autenticación. V2 solo enviaba la ruta inicial desde la función onLoad del iframe, después de que V1 instalara su receptor de mensajes, y enviaba los cambios posteriores únicamente cuando cambiaba la ruta asignada.
iframe.contentWindow?.postMessage(
{
action: 'NAVIGATE',
route: iframeRoute,
location_id: locationId,
migratedLocationIds: [...MIGRATED_LOCATION_IDS]
},
dashboardUrl
)Haz que dos frontends se comporten como uno
Definimos de forma explícita ambas direcciones de los mensajes. V2 enviaba navegación, tokens SSO y datos de usuario y ubicación. V1 devolvía cambios de ruta, solicitudes de datos del usuario, solicitudes de renovación de tokens y acciones Click2Call.
Cada receptor rechazaba los orígenes inesperados y cada emisor apuntaba al origen configurado de la aplicación en lugar de usar un comodín. Las comprobaciones de origen eran el primer límite, no una validación completa de la carga útil. Los mensajes que contenían tokens también necesitaban tipos conocidos y campos validados.
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)Un inicio de sesión tenía que autenticar ambas aplicaciones
El usuario iniciaba sesión una vez en V2. Cuando se cargaba V1, solicitaba el contexto actual; V2 devolvía el token SSO y los datos del usuario y la ubicación. V1 guardaba el token para su cliente de API existente y solicitaba uno nuevo mediante el mismo puente cuando caducaba.
Se introdujo un hook de autenticación proactiva mientras se diagnosticaba una condición de carrera al iniciar el iframe. Intentaba enviar la autenticación antes de que V1 la pidiera, pero añadió otra ruta de temporización y terminó activándose demasiado pronto. El cambio posterior eliminó ese hook y conservó la autenticación reactiva de solicitud y respuesta, con un único responsable claro del momento de renovación del token.
Mantén veraces la barra de direcciones y el historial del navegador
V2 asignaba sus rutas a rutas de V1 como /dashboard, /buyer-genie, /deals y /my-buyers. La asignación inversa atendía tanto rutas exactas como prefijos anidados. Cuando la navegación comenzaba dentro de V1, V2 usaba router.replace para actualizar la barra de direcciones y la navegación activa sin añadir una segunda entrada al historial por una sola acción del usuario.
Esto mantuvo coherentes la actualización, los enlaces profundos y la navegación atrás y adelante. La acción Atrás del navegador cambiaba la ruta padre, el padre enviaba la ruta hija asignada y el iframe montado navegaba sin recargar el contenedor principal.
Traslada clientes y funcionalidades en segmentos controlados
MIGRATED_LOCATION_IDS era el selector del despliegue gradual. Empezamos con ubicaciones internas y de staging y después ampliamos a ocho ubicaciones de clientes. Todos los que quedaban fuera de la lista seguían usando la ruta V1 existente.
- Nativo en V2: Navbar, Topbar, Settings, Investor Intake, SSO y Twilio Dialer.
- Todavía en V1 en el punto medio registrado: Home, Genius Mode, Deals y Buyers.
- Unidad de migración: una función y ubicaciones de clientes seleccionadas, no un lanzamiento independiente.
El reemplazo permaneció invisible durante el despliegue
La capa de compatibilidad finalmente se eliminó en lugar de convertirse en arquitectura permanente. La transición borró el sistema de iframe y los clientes de API heredados generados, trasladó los clientes restantes a V2, añadió la ruta nativa de inicio de sesión y después limpió rutas, pruebas, código compartido del marcador, protecciones de autenticación y estructura de componentes.
Durante un tiempo mantuvimos dos enrutadores, dos modelos de sesión, eventos entre ventanas, asignaciones de rutas y condiciones de carrera al arrancar. Ese costo solo era aceptable porque cada ruta migrada nos acercaba a eliminar el puente.

