يضغط المستخدمون على زر الرجوع بشكل متكرر أكثر من أي عنصر تحكم آخر تقريباً في المتصفح. وهم يتوقعون ظهور الشاشة السابقة فوراً، تماماً من حيث توقفوا. وتلبي المتصفحات الحديثة هذا التوقع باستخدام ذاكرة التخزين المؤقت للرجوع/للأمام، أو ما يعرف بـ bfcache. فبدلاً من تدمير الصفحة عند الانتقال بعيداً عنها، يقوم المتصفح بتجميدها في الذاكرة. وعندما تعود، فإنه يستعيد لقطة (snapshot) منها. يتخطى المتصفح عمليات تحليل HTML، وإعادة تنفيذ JavaScript، وإعادة حساب التخطيط (layout). وتبدو النتيجة فورية لأن الصفحة لم تمت تماماً.

ما يفعله bfcache فعلياً

عملية تحميل الصفحة العادية مكلفة وتستهلك موارد كثيرة؛ إذ يجب على المتصفح جلب الموارد، وتحويل HTML إلى رموز (tokenize)، وبناء الـ DOM، وتشغيل البرامج النصية (scripts)، وتطبيق الأنماط (styles)، وإجراء التخطيط (layout)، ورسم البكسلات، ودمج الطبقات. يتجاوز bfcache كل ذلك تقريباً عن طريق إبقاء الصفحة حية في حالة مجمدة في ذاكرة الوصول العشوائي (RAM). وهي ليست ذاكرة تخزين مؤقت على القرص (disk cache). فالصفحة التي تم عرضها، بما في ذلك ذاكرة JavaScript heap، وموضع التمرير، وحالة النموذج (form state)، تظل في الذاكرة بينما يقرأ المستخدم الصفحة التالية. وعندما ينقر المستخدم على زر الرجوع، يقوم المتصفح بإذابة اللقطة وإطلاق حدث pageshow. وتستأنف الصفحة عملها دون المساس بالشبكة أو إعادة إجراء التخطيط من الصفر. وبالنسبة للمستخدمين الذين يستخدمون أجهزة بطيئة أو اتصالات متقطعة، فإن الفرق بين استعادة الصفحة عبر bfcache وتحميلها من جديد قد يصل إلى مئات الملي ثانية أو أكثر.

ما الذي يعطله

أجرى أحد المطورين مؤخراً تجربة دقيقة لمعرفة ما الذي يمنع bfcache بالضبط. حيث قام ببناء ست صفحات بسيطة، كل منها يختبر عائقاً واحداً مشتبهاً به، ثم انتقل بعيداً وضغط على زر الرجوع. وكانت النتائج واضحة.

استعادت الصفحة الأساسية (baseline) التي لا تحتوي على رؤوس (headers) أو برامج نصية غير عادية نفسها بنجاح. كما استعادت الصفحة التي تحتوي على مستمع لحدث beforeunload نفسها دون مشاكل. ومن المثير للدهشة أن الصفحة التي يتم تقديمها مع Cache-Control: no-store دخلت أيضاً في bfcache، مما يناقض التوجيهات القديمة. وحتى مقال مدونة مباشر، والذي قد يبدو ديناميكياً للغاية بحيث يصعب تجميده، استعاد نفسه بنجاح.

فشلت صفحتان فقط: صفحة تحتوي على مستمع لحدث unload لم تتمكن من استعادة نفسها، وصفحة تحتوي على اتصال WebSocket مفتوح تم حظرها أيضاً. وتشير هاتان الحالتان من الفشل إلى الفخاخ التي تقع فيها مواقع الإنتاج الحقيقية كل يوم.

فخ حدث unload

لطال عمليات التنظيف في اللحظة الأخيرة باستخدام حدث unload الاعتماد كإشارة أساسية. يستخدمه المطورون لإرسال إشارات التحليلات (analytics beacons)، أو إيقاف المؤقتات، أو مسح الحالة المؤقتة. المشكلة هي أن bfcache مبني على فكرة أن الصفحة قد تعود للحياة. فإذا رأى المتصفح مستمعاً لحدث unload فإنه يفترض أن الصفحة تتوقع تدميراً كاملاً ويرفض تجميدها. ولا يهم إذا كانت الدالة المرفقة فارغة؛ فمجرد وجود المستمع كافٍ لمنع التخزين المؤقت في كل المتصفحات الحديثة.

البديل هو pagehide. يتم إطلاق هذا الحدث سواء عند تجميد الصفحة من أجل bfcache أو عند التخلص منها فعلياً. إذا كنت بحاجة للتمييز بين الاثنين، فإن خاصية event.persisted تكون true عندما تتجه الصفحة إلى bfcache. ومع ذلك، فإن معظم مهام التفكيك (teardown) يغطيها pagehide في كلا المسارين. انقل كل منطق التنظيف من unload إلى pagehide. ثم قم بإزالة كل مستمع لحدث unload تماماً، بما في ذلك تلك الموجودة داخل قصاصات التحليلات التابعة لجهات خارجية أو الإضافات القديمة.

فخاخ الاتصالات النشطة

يشير وجود اتصال شبكة أو تخزين مفتوح إلى أن صفحتك لا تزال تقوم بعمل حقيقي. يقوم المتصفح بجرد الموارد النشطة في لحظة الانتقال. فإذا وجد اتصال WebSocket مفتوحاً، أو اتصال WebRTC نشطاً، أو اتصال IndexedDB عالقاً، فإنه يلغي عملية التجميد وينهي الصفحة بشكل طبيعي. لا يمكن الوثوق باللقطة (snapshot) بينما لا تزال البيانات تتدفق.

يجب عليك إغلاق هذه الموارد داخل مستمع pagehide. استدعِ طريقة close الخاصة بـ WebSocket الخاص بك. أغلق اتصالات WebRTC peer. قم بإلغاء أو إتمام أي عمليات IndexedDB معلقة. إذا كان تطبيقك يحتاج إلى تلك القنوات عند عودة المستخدم، فأعد فتحها داخل pageshow. يحافظ نمط "الإغلاق عند pagehide والاستعادة عند pageshow" على أهلية الصفحة للتنقل الفوري للخلف دون فقدان الوظائف.

مفاجأة no-store

لسنوات، ساد اعتقاد بأن Cache-Control: no-store يمنع bfcache. لكن Chrome غير هذا السلوك في عام 2025. حيث يمكن الآن للصفحة التي تُقدم مع no-store الدخول في bfcache. لا يقوم المتصفح بإخراج اللقطة المجمدة إلا لاحقاً إذا تغيرت حالات المصادقة أو ملفات تعريف الارتباط (cookies) بطريقة تبطل الحالة المحفوظة. إذا كنت تستخدم no-store كـ...