الانتقال إلى المحتوى

ترحيل الواجهة الأمامية من دون توقف

إعادة كتابة الواجهة الأمامية. 170 موقعًا لا تزال تستخدمها. بلا توقف.

ترحيل حي من CRA إلى Next.js باستخدام غلاف V2 وجسر iframe ومصادقة مشتركة ومسارات متزامنة وطرح مرحلي.

قراءة في 5 دقائقبقلم

لم يكن ممكنًا إيقاف المنتج

ظل CRA V1 يخدم 170 موقعًا ونحو 765 مستخدمًا متزامنًا. لم يكن بوسعنا تجميد تطوير الميزات، وبناء بديل بمعزل، ثم نقل جميع العملاء دفعة واحدة. كان على الترحيل أن يجري داخل المنتج الحي.

استخدمنا نمط Strangler Fig: وضعنا النظام الجديد حول القديم، ونقلنا مسؤولية إنتاجية واحدة في كل مرة، ثم حذفنا طبقة التوافق عندما لم يعد شيء يعتمد عليها.

استخدم نمط strangler بدل إطلاق ثانٍ

أصبح Next.js V2 غلاف التطبيق. امتلك شريط التنقل والشريط العلوي والمصادقة والإطار العام. واستمرت المسارات غير المرحلة داخل iframe واحد لتطبيق CRA، لذلك رأى العملاء منتجًا واحدًا بينما كانت بيئتا واجهة أمامية تعملان.

كان iframe يحمّل دائمًا عنوان لوحة معلومات أساسيًا ثابتًا، ويظل مثبتًا عبر تغيرات مسار التطبيق الأب؛ فإعادة تركيبه كانت ستصفر حالة التطبيق القديم وتكرر المصادقة. أرسل V2 المسار الأولي فقط من رد onLoad في iframe، بعد أن ثبت V1 مستمع الرسائل، ثم أرسل التغييرات اللاحقة فقط عندما تغير المسار المطابق فعلًا.

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

اجعل واجهتين تتصرفان كواجهة واحدة

عرّفنا اتجاهَي الرسائل بوضوح. أرسل V2 التنقل ورموز SSO وبيانات المستخدم والموقع. وأعاد V1 تغيرات المسار وطلبات بيانات المستخدم وطلبات تحديث الرمز وإجراءات Click2Call.

رفض كل مستقبِل المصادر غير المتوقعة، واستهدف كل مرسِل مصدر التطبيق المعدّ بدل استخدام حرف بدل عام. كانت فحوص المصدر أول حد أمان، لا تحققًا كاملًا من الحمولة. واحتاجت الرسائل التي تحمل رموز مصادقة أيضًا إلى أنواع رسائل معروفة وحقول متحقق منها.

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)

كان على تسجيل دخول واحد مصادقة التطبيقين

سجل المستخدم دخوله مرة واحدة في V2. وعندما حُمّل V1، طلب سياق المستخدم الحالي؛ فأعاد V2 رمز SSO مع بيانات المستخدم والموقع. خزّن V1 الرمز لعميل API القائم وطلب بديلًا عبر الجسر نفسه بعد انتهاء صلاحيته.

أُدخل خطاف مصادقة استباقي أثناء تشخيص سباق في بدء iframe. حاول دفع المصادقة قبل أن يطلبها V1، لكنه أضاف مسار توقيت آخر وأصبح مفرط الحماس. أزال التغيير اللاحق ذلك الخطاف وأبقى مصادقة الطلب والاستجابة التفاعلية، فظل لتوقيت الرمز مالك واحد واضح.

حافظ على تطابق شريط العنوان وسجل المتصفح مع الواقع

ربط V2 مساراته بمسارات V1 مثل /dashboard و/buyer-genie و/deals و/my-buyers. وتعامل الربط العكسي مع المسارات المطابقة والبادئات المتداخلة. وعندما بدأ التنقل داخل V1، استخدم V2 الأمر router.replace كي يتغير شريط العنوان والتنقل النشط من دون إضافة سجل ثانٍ لإجراء مستخدم واحد.

أبقى ذلك التحديث والروابط العميقة وأزرار الرجوع والتقدم في المتصفح متسقة. غيّر إجراء Back مسار التطبيق الأب، فأرسل الأب مسار الطفل المطابق، وانتقل iframe المثبت من دون إعادة تحميل الغلاف.

انقل العملاء والميزات على دفعات مضبوطة

كان MIGRATED_LOCATION_IDS مفتاح الطرح. بدأنا بالمواقع الداخلية وبيئة staging، ثم توسعنا إلى ثمانية مواقع للعملاء. وبقي كل من هو خارج القائمة على مسار V1 القائم.

  • أصلي في V2: Navbar وTopbar وSettings وInvestor Intake وSSO وTwilio Dialer.
  • ما زال في V1 عند نقطة المنتصف المسجلة: Home وGenius Mode وDeals وBuyers.
  • وحدة الترحيل: ميزة واحدة ومواقع عملاء مختارة، لا إطلاق منفصل واحد.

ظل الاستبدال غير مرئي أثناء الطرح

أُزيلت طبقة التوافق في النهاية بدل أن تصبح بنية دائمة. حذف الانتقال نظام iframe وعملاء API المولدين القدامى، ونقل العملاء الباقين تحت V2، وأضاف مسار تسجيل الدخول الأصلي، ثم نظف المسارات والاختبارات وشفرة الاتصال المشتركة وحراس المصادقة وبنية المكونات.

حافظنا فترةً على موجّهين ونموذجَي جلسة وأحداث بين النوافذ وخرائط مسارات وسباقات بدء. لم تكن تلك الكلفة مقبولة إلا لأن كل مسار مرحل قرّبنا من حذف الجسر.