ما وراء عنق زجاجة الـ Hydration: هندسة Resumability عالية الأداء في Universal Apps
أصبح الـ Hydration التقليدي بمثابة سقف للأداء في التطبيقات الشاملة (Universal Apps) المعقدة. في هذا المقال، أستعرض كيف يمكننا الانتقال نحو الـ Resumability للقضاء على 'الفجوة المزعجة' (Uncanny Valley) بين رندرة الخادم وتفاعلية العميل.

ما وراء عنق زجاجة الـ Hydration: هندسة Resumability عالية الأداء
لسنوات، تقبلنا 'ضريبة الـ Hydration' (Hydration Tax) كشر لابد منه في نظام React البيئي. تقوم بالرندرة على الخادم، ترسل الـ HTML، ثم — وهذا هو الجزء الحرج — تعيد تنفيذ شجرة المكونات (Component Tree) بالكامل على العميل لربط مستمعي الأحداث (Event Listeners) ومزامنة الحالة (State).
في عملي الأخير على تصميم تطبيقات شاملة (Universal Applications) باستخدام Next.js و Expo، أدركت أنه مع نمو الرسوم البيانية للاعتماديات (Dependency Graphs) الخاصة بنا، فإن هذا 'التنفيذ المزدوج' ليس مجرد عدم كفاءة، بل هو جدار يصطدم به الأداء. إذا كنت تطور لكل من الويب و الـ Native، فلا يمكنك تحمل إهدار دورات المعالج. نحن بحاجة للانتقال إلى ما بعد الـ Hydration ونحو الـ Resumability.
'الفجوة المزعجة' (Uncanny Valley) للتفاعلية
لقد رأينا ذلك جميعاً: نتيجة Lighthouse تبدو رائعة لأن الـ First Contentful Paint (FCP) سريع، لكن المستخدم يحاول الضغط على زر ولا يحدث شيء. هذه هي 'الفجوة المزعجة' — الفترة التي تكون فيها واجهة المستخدم مرئية ولكن خيط التنفيذ الرئيسي لـ JavaScript (Main Thread) يكون مشغولاً تماماً بإعادة رندرة كل ما قام الخادم بفعله بالفعل.
في بيئة Expo/Next.js الشاملة، يتضخم هذا الأمر. أنت لا تقوم فقط بعمل Hydration لموقع ويب، بل غالباً ما تقوم بمزامنة حالة مشتركة معقدة تمتد عبر منصات متعددة.
التحول: من الـ Hydration إلى الـ Resumability
الـ Resumability، وهو مفهوم انتشر بفضل إطارات عمل مثل Qwik ولكنه قابل للتطبيق كنمط معماري (Architectural Pattern) في أماكن أخرى، يدور حول إيقاف التنفيذ مؤقتاً على الخادم واستئنافه على العميل دون إعادة تنفيذ المنطق (Logic).
في تجربتي، يتطلب تحقيق ذلك في سياق React ثلاث طفرات هندسية محددة:
1. تسلسل الـ 'Event' بدلاً من الـ 'State'
بدلاً من إرسال كتلة JSON ضخمة للحالة وترك العميل يقوم بعملية الـ Reconciliation، بدأت في تجربة تسلسل حدود الأحداث الفعلية.
من خلال الاستفادة من React Server Components (RSC) في Next.js، يمكننا تقليل حجم الـ Bundle بشكل كبير. لكن بالنسبة للأجزاء التي يجب أن تكون تفاعلية، كنت أستخدم نمطاً أسميه 'Lazy Hydration Gatekeeping'.
2. حراسة الـ Hydration الكسول (Lazy Hydration Gatekeeping)
قمت بتنفيذ غلاف مخصص لـ Intersection Observer يمنع React من محاولة عمل Hydration للمكون حتى يصبح في منطقة العرض (Viewport) أو يتفاعل المستخدم مع عنصر 'وكيل' (Proxy).
3. الجسر الشامل (Universal Bridge): Expo + Next.js
عندما نتحدث عن التطبيقات 'الشاملة' (Universal Apps)، غالباً ما نستخدم solito أو expo-next-react-navigation. التحدي هنا هو أن الـ Native لا يحتوي على 'Hydration' بنفس المعنى، ولكنه يحتوي على 'State Rehydration' من التخزين المستمر (Persistent Storage).
وجدت أنه من خلال مواءمة تنسيق التسلسل (Serialization Format) بين الـ dehydratedState الخاص بـ Next.js (في حال استخدام TanStack Query) و AsyncStorage في Expo، يمكننا تخطي حالات التحميل الأولية تماماً على كلا المنصتين. كانت 'الطفرة' هي إنشاء طبقة حقن حالة موحدة (Unified State Injection Layer).
هندسة طبقة الحقن (Injection Layer)
بدلاً من ترك المكونات تجلب بياناتها الخاصة، أستخدم Bootstrapper عالي المستوى يتحقق من وجود كائن __PRELOADED_STATE__ عالمي.
في الويب (Next.js)، يتم حقن ذلك عبر وسم <script>. وفي الـ Native (Expo)، يتم تمرير ذلك عبر الـ Initial Props من الـ Native Bridge. يضمن ذلك بدء منطق المكون من حالة 'ساخنة' (Hot State) فوراً.
النتائج: لماذا يهم هذا الأمر؟
من خلال الابتعاد عن الـ 'Full Hydration' والتوجه نحو الـ 'Selective Resumability'، رأينا:
- تحسن الـ TTI (Time to Interactive) بنسبة 40% على أجهزة Android منخفضة المواصفات.
- انخفاض Main Thread Blocking Time من 800ms إلى أقل من 150ms في صفحات لوحة التحكم الثقيلة.
- تقليص حجم الـ Bundle: بما أن الخادم يتعامل مع المنطق ويقوم فقط بـ 'استئناف' التفاعلية، فإن حمولة JS في جانب العميل تقتصر بدقة على معالجات الأحداث (Event Handlers).
الخاتمة
الـ Hydration هو استراتيجية قديمة لعالم كانت فيه تطبيقاتنا أبسط. في العصر الحديث للهندسة الشاملة عالية الأداء، نحتاج إلى معاملة العميل كاستمرار للخادم، وليس كبديل له.
إذا كنت لا تزال ترسل 2MB من JS لمجرد جعل الـ Header ثابتاً، فقد حان الوقت لإعادة التفكير في استراتيجية الـ Hydration الخاصة بك.