الذاكرة المؤقتة للمتصفح
الذاكرة المؤقتة للمتصفح (Browser Cache) هي مجموعة النسخ التي يحتفظ بها المتصفح من ملفات نزّلها من قبل: صفحات HTML والسكربتات وملفات التنسيق والصور والخطوط وبعض استجابات الواجهات البرمجية. وعندما يحتاج إلى ملف مرة أخرى ويكون استخدام النسخة المخزّنة ما زال مسموحاً، يأخذ النسخة بدلاً من تنزيل الملف من جديد. فتصبح الزيارات اللاحقة أسرع، وقد يرى المستخدم العائد شيئاً مختلفاً عمّا يراه زائر لأول مرة.
مثال: يطلق متجر زر «Pay now» باللون الأخضر الساعة 10:00. ويفتح عميل زار المتجر أمس صفحة الدفع الساعة 11:00 فيرى الزر الأزرق القديم، لأن المتصفح أعاد استخدام ملف التنسيق المخزّن منذ أمس دون أن يسأل الخادم. الإصدار يعمل، لكنه ببساطة لم يصل إلى هذا العميل.
لماذا يهم
المحتوى القديم (Stale Content) سبب شائع لعبارة «يعمل عندي» بعد الإصدار: فالمختبِر تحقق بمتصفح نظيف، بينما احتفظ العملاء بملفات قديمة. وتتراوح الآثار بين شعار قديم وإخفاقات خطيرة: HTML جديد يعمل مع JavaScript قديم يرسل طلبات لم يعد الخادم يقبلها، أو صفحة حساب تبقى ظاهرة بعد تسجيل الخروج.
والتخزين المؤقت أمر يضبطه الفريق: فترويسات الاستجابة وإعدادات البناء وشيفرة عامل الخدمة هي التي تقرر ما يُخزَّن ولأي مدة، وإذا خلت الاستجابة من ترويسات التخزين المؤقت فقد يقدّر المتصفح عمرها بنفسه. والإعداد الخاطئ أو الغائب عيب كأي عيب آخر. وإذا اختبرت دائماً بذاكرة مؤقتة فارغة فلن ترى أبداً ما يراه معظم مستخدميك.
التخزين في الذاكرة وعلى القرص وعمّال الخدمة
في درس «كيف تعمل المتصفحات» رأيت Chrome يكتب (memory cache) أو (disk cache) على بعض صفوف لوحة Network. فالذاكرة المؤقتة في الذاكرة (memory cache) تحفظ ملفات استخدمتها الصفحة المفتوحة من قبل، وهي سريعة وقصيرة العمر ولا تبقى بعد إغلاق التبويب. أما الذاكرة المؤقتة على القرص (disk cache) فهي ذاكرة HTTP المؤقتة للمتصفح، أي ذاكرة خاصة (private cache) بتعبير RFC 9111: تبقى بعد إعادة التشغيل، وترويسات الاستجابة هي التي تحدد ما يجوز لها تخزينه ولأي مدة.
النسخة الحديثة (fresh)، كتلك التي ما زالت ضمن عمرها المحدد بـ Cache-Control: max-age، تُستخدم دون الاتصال بالخادم. أما النسخة المتقادمة (stale) فيُعاد التحقق منها عادةً: يرسل المتصفح وسم إصدار النسخة (ETag) في If-None-Match، أو تاريخها (Last-Modified) في If-Modified-Since، فيجيب الخادم بـ 304 Not Modified (احتفظ بالنسخة) أو بـ 200 مع نسخة جديدة. وتعني no-cache «خزّن، لكن أعد التحقق قبل كل استخدام»، وتعني no-store «لا تخزّن». ويشرح درس التخزين المؤقت في HTTP هذه الترويسات بالتفصيل.
عامل الخدمة (Service Worker) سكربت يسجّله الموقع في المتصفح. يقف بين الصفحة والشبكة، ويستطيع الإجابة عن الطلبات من مخزنه الخاص Cache Storage حتى لو كانت ذاكرة HTTP المؤقتة فارغة. وافتراضياً يُثبَّت الإصدار الجديد منه في الخلفية وينتظر حتى تُغلق كل التبويبات التي تستخدم الإصدار القديم، لذلك لا تكفي إعادة التحميل العادية لتبديل الإصدار.
الإجابة: نعم
النتيجة: يجيب عامل الخدمةتختار شيفرته بين Cache Storage والشبكة، فقد يقدّم إصداراً قديماً(ServiceWorker)الإجابة: لا
سؤال 2: هل لدى المتصفح نسخة مخزّنة؟في الذاكرة المؤقتة في الذاكرة أو على القرصالإجابة: لا
النتيجة: لا نسخة (miss): يُنزَّل من الخادمثم يُخزَّن إن سمحت ترويسات الاستجابة بذلك200الإجابة: نعم
سؤال 3: هل ما زالت النسخة حديثة؟ضمنmax-ageوغير موسومة بـno-cacheالإجابة: نعم
النتيجة: إصابة (hit): تُستخدم دون سؤال الخادمتظهر بـ(memory cache)أو(disk cache)؛ ولا يصل إليها نشر جديد على العنوان نفسهالإجابة: لا
النتيجة: يُعاد التحقق منها مع الخادم304: تبقى النسخة؛200: تحل محلها نسخة جديدةIf-None-Match
نموذج مفاهيمي: تختلف المتصفحات في التفاصيل؛ فمثلاً يبحث Chrome في ذاكرته المؤقتة قصيرة العمر في الذاكرة حتى قبل عامل الخدمة. وإعادة التحميل القسرية تتجاوز النسخ المخزّنة وعامل الخدمة في تحميل واحد ولا تحذف شيئاً.
- إعادة التحميل القسرية (Hard Reload) بـ Ctrl+Shift+R، أو Cmd+Shift+R على Mac: تطلب الصفحة وملفاتها من جديد وتتجاوز عامل الخدمة، في هذا التحميل وحده. ولا تحذف شيئاً، وقد تعيد إعادة التحميل العادية استخدام النسخ الحديثة.
- «Empty Cache and Hard Reload» في Chrome (انقر بالزر الأيمن على زر إعادة التحميل وأدوات المطوّرين مفتوحة): تفرغ الذاكرة المؤقتة ثم تعيد التحميل قسرياً.
- «Disable cache» في لوحة Network: لا ذاكرة HTTP مؤقتة ما دامت أدوات المطوّرين مفتوحة، لكن عامل الخدمة يستطيع أن يجيب رغم ذلك.
- لوحة Application: يعرض قسم Service workers الإصدار النشط وأي إصدار منتظر، مع «Bypass for network» و«Unregister»؛ ويزيل «Clear site data» عامل الخدمة ومخزنه Cache Storage أيضاً.
- النافذة الخاصة: ذاكرة مؤقتة فارغة ولا عامل خدمة، أي ما يراه زائر لأول مرة. أغلق النوافذ الخاصة الأخرى أولاً، فهي تتشارك جلسة واحدة وذاكرتها المؤقتة.
كسر الذاكرة المؤقتة: كيف يصل الإصدار إلى المتصفح
تريد الفرق شيئين معاً: ملفات تُنزَّل مرة واحدة ويُعاد استخدامها مدة طويلة، وإصدارات يتلقاها كل مستخدم فوراً. وكسر الذاكرة المؤقتة (Cache Busting) يحقق الأمرين: عندما يتغير الملف يتغير عنوانه. فأدوات البناء تضع بصمة (fingerprint)، أي تجزئة (hash) قصيرة محسوبة من محتوى الملف، في اسم الملف، فيصبح app.3f9c1e.js هو app.9b41d2.js؛ وتضيف بعض الفرق رقم الإصدار إلى سلسلة الاستعلام بدلاً من ذلك (app.js?v=2.15.0). ولم يخزّن المتصفح العنوان الجديد من قبل، فلا بد أن ينزّله.
ولا ينجح هذا إلا إذا كانت صفحة HTML التي تذكر أسماء الملفات حديثة هي نفسها. اقرأ الإعداد أدناه هكذا:
- تُخدَم صفحة HTML بـ
no-cache: فيُعاد التحقق منها في كل زيارة وتصل أسماء الملفات الجديدة. - يمكن تخزين الملفات ذات البصمة سنة كاملة، لأن أي تغيير يمنحها اسماً جديداً.
- الملف الذي بلا بصمة، مثل
logo.svg، يبقى قديماً بعد النشر مدةً تصل إلى قيمةmax-ageالخاصة به. - التبويب المفتوح قبل النشر ما زال يشغّل الشيفرة القديمة. فإذا طلب لاحقاً ملفاً حذفه النشر، مثل سكربت شاشة أخرى، يفشل الطلب بـ
404؛ لذلك تُبقي الفرق ملفات الإصدار السابق متاحة مدةً بعد النشر.
Request Cache-Control After a deploy
GET /checkout no-cache revalidated: new HTML
GET /assets/app.9b41d2.js max-age=31536000, immutable new name: downloaded
GET /assets/app.51c7aa.css max-age=31536000, immutable new name: downloaded
GET /images/logo.svg max-age=86400 old for up to 1 day
GET /api/cart no-store never storedجرّبها: محلّل ترويسات التخزين المؤقت جرّبها: منشئ ترويسات HTTP
عيوب المحتوى القديم وكيف تتعرّف عليها
إخفاق المحتوى القديم يُظهر نسخة من صفحة أو ملف أو بيانات أقدم مما يحمله الخادم الآن. والعيب وراءه يكون غالباً في إعداد التخزين المؤقت لا في الميزة قيد الاختبار: بصمة مفقودة، أو max-age أطول مما يجب، أو صفحات HTML أو استجابات واجهات برمجية ما كان ينبغي تخزينها، أو عامل خدمة لا يُحدَّث أبداً. وانتبه إلى معنيَي «القِدم» هنا: النسخة التي وراء هذا الإخفاق تكون غالباً ما زالت حديثة (fresh) بحسب ترويساتها، فلا يسأل المتصفح الخادم عنها. أما «متقادمة» (stale) في RFC 9111 فتعني فقط أن عمر النسخة انتهى، والنسخة المتقادمة يُتحقق منها مع الخادم.
ولتثبت أن الذاكرة المؤقتة هي السبب، قارن: كرّر الخطوات في نافذة خاصة، واقرأ عمود Size حيث تحل (memory cache) أو (disk cache) أو (ServiceWorker) محل عدد البايتات، وقارن أسماء الملفات ورقم البناء بما نُشر فعلاً. ومن الأعراض المعتادة:
- جزء من الإصدار مفقود لدى بعض المستخدمين: HTML جديد مع تنسيقات قديمة، أو ميزة تُظهرها إعادة التحميل القسرية ولا يراها زميلك.
- بيانات قديمة: تغيّر سعر في لوحة الإدارة، لكن المتجر يعرض السعر القديم لأن استجابة الواجهة البرمجية خُزّنت في المتصفح أو في شبكة توصيل المحتوى (CDN)، وهي مجموعة ذاكرات مؤقتة مشتركة بين المستخدمين والموقع.
- إصدارات مختلطة: JavaScript قديم يستدعي واجهة برمجية تغيّرت، فيفشل الحفظ بـ
400أو بخطأ في وحدة التحكم. - موقع عالق على إصدار قديم، أو يعرض صفحة عدم الاتصال وأنت متصل: عامل خدمة يقدّم نسخه المخزّنة.
- بيانات شخصية بعد تسجيل الخروج: الضغط على زر الرجوع يُظهر صفحة الحساب مرة أخرى.
مثال تطبيقي: اختبار إصدار كمستخدم عائد
يغيّر الإصدار 2.15.0 لأحد المتاجر زرَّ الدفع وتنسيقاته. وتحتفظ المختبِرة بملف تعريف (profile) في المتصفح يؤدي دور عميل عائد. قبل النشر تفتح صفحة الدفع فيه وتحفظ قائمة أسماء الملفات المحمّلة، وبعد النشر تقارن هذه الذاكرة المؤقتة الممتلئة بذاكرة فارغة:
- افتح صفحة الدفع في ملف التعريف ذاك بتنقّل عادي و«Disable cache» غير مفعّلة، وسجّل ما تراه ورقم البناء.
- قارن صفوف لوحة Network بالقائمة المحفوظة: هل يصل كل ملف تغيّر من الخادم باسم جديد، أم من
(disk cache)بمحتوى قديم؟ - في تبويب بقي مفتوحاً منذ ما قبل النشر، افتح شاشة لم تزرها بعد وأرسل نموذجاً.
- إن كان للموقع عامل خدمة، فابحث عن إصدار منتظر في Application > Service workers؛ ثم أغلق كل تبويبات الموقع وأعد فتحه وتأكد أن الإصدار الجديد هو النشط.
- افتح نافذة خاصة: يجب أن يحصل الزائر لأول مرة على الإصدار الجديد أيضاً.
Release 2.15.0 on staging, returning-customer profile, normal navigation
Name Status Size Note
checkout 200 5.2 kB new HTML, footer shows build 2.15.0
app.9b41d2.js 200 212 kB new fingerprint
styles.css 200 (disk cache) no fingerprint, max-age=604800
logo.svg 200 (disk cache) not changed in 2.15.0: fine
النتيجة: الزر «Pay now» أزرق هنا (تنسيقات قديمة) وأخضر في نافذة خاصة.
العيب: الملف styles.css بلا بصمة ويُخزَّن 7 أيام، فيرى العملاء العائدون
صفحة الدفع في 2.15.0 بتنسيقات 2.14 حتى تنتهي صلاحيته.أخطاء شائعة
- اختبار الإصدار بذاكرة مؤقتة فارغة فقط (نافذة خاصة، أو إعادة تحميل قسرية، أو «Disable cache» مفعّلة دائماً)، فلا ترى أبداً ما يراه المستخدمون العائدون.
- الرد على بلاغ محتوى قديم بعبارة «امسح الذاكرة المؤقتة». المستخدمون لا يمسحونها؛ أبلغ عن عيب التخزين المؤقت بدلاً من ذلك.
- نسيان عامل الخدمة: تبدو الصفحة صحيحة بعد إعادة التحميل القسرية، ثم يقدّمها عامل الخدمة القديم في التحميل العادي التالي.
- افتراض أن الصفحة تعرض البناء الجديد. تحقّق من رقم البناء أو بصمات الملفات قبل أن تختبر أي شيء آخر.
- إغفال حالة الذاكرة المؤقتة في تقرير الخطأ: قيمة عمود Size، وأسماء الملفات وترويساتها، والبناء، وهل أعدت التحميل قسرياً.
من منظور ضمان الجودة
اطرح أسئلة التخزين المؤقت أثناء تحسين القصص (refinement) كلما مسّت قصةٌ الواجهة الأمامية: أي الملفات لها بصمة؟ وما ترويسة Cache-Control التي تحصل عليها صفحات HTML والملفات واستجابات الواجهات البرمجية؟ وكم تبقى الملفات القديمة متاحة؟ وكيف يُحدَّث عامل الخدمة ويطلب من المستخدمين إعادة التحميل؟
بعد كل نشر، اختبر أولاً بملف تعريف لمستخدم عائد، وأضف فحصاً واحداً يتجاوز كل ذاكرة مؤقتة، ثم قارن بينهما. وفي تقرير المحتوى القديم سجّل البناء المتوقع والبناء الظاهر، والعنوان المتأثر مع Cache-Control وقيمة عمود Size، والمتصفح وملف التعريف، وهل تُظهر النافذة الخاصة الإصدار الجديد. عندها يجد المطورون الملف ذا الإعداد الخاطئ بدلاً من التخمين.
تمرين عملي
توقّع ما يراه العميل العائد بعد الإصدار
يضيف الإصدار 3.4.0 سطر ضريبة القيمة المضافة إلى صفحة الفاتورة، ويغيّر لون العلامة التجارية، ويستبدل الشعار. وقد نُشر يوم الثلاثاء الساعة 10:00. فتح أحد العملاء الفاتورة مرة واحدة من قبل، يوم الاثنين الساعة 18:00، فخزّن المتصفح كل ملفاتها حينها، ويعود يوم الثلاثاء الساعة 11:00 بتنقّل عادي. لا يستخدم الموقع عامل خدمة.
باستخدام الترويسات أدناه، حدّد لكل طلب هل يستخدم المتصفح نسخته المخزّنة أم الخادم، وصِف ما يراه العميل، وسمِّ العيوب وكيف تؤكدها.
Release 3.4.0, live Tuesday 10:00. No service worker.
Request Cache-Control Note
GET /invoice/1001 no-cache ETag changed in 3.4.0
GET /static/invoice.81f2a0.js max-age=31536000, immutable new name in 3.4.0
GET /static/styles.css max-age=604800 changed in 3.4.0
GET /static/logo.png max-age=86400 changed in 3.4.0
GET /api/invoices/1001 no-store VAT line data- احسب حتى متى تبقى كل نسخة مخزّنة حديثة.
- حدّد لكل طلب: تنزيل، أو إعادة تحقق، أو نسخة مخزّنة، أو لا تخزين أبداً.
- صِف الصفحة التي يراها العميل.
- سمِّ العيوب وفحصاً يؤكدها.
إظهار الإجابة النموذجية
الأجزاء الجديدة تصل: يُعاد التحقق من /invoice/1001 بسبب no-cache، ولأن قيمة ETag تغيّرت تصل صفحة HTML الجديدة مع سطر الضريبة؛ وinvoice.81f2a0.js عنوان لم يخزّنه المتصفح من قبل فيُنزَّل؛ واستجابة الواجهة البرمجية ذات no-store تأتي دائماً من الخادم.
الأجزاء القديمة تبقى: خُزّن styles.css يوم الاثنين الساعة 18:00 مع max-age=604800 (7 أيام)، فيبقى حديثاً حتى الاثنين التالي الساعة 18:00 ويُستخدم دون سؤال الخادم. أما logo.png فله max-age=86400 (يوم واحد) ويبقى حديثاً حتى الثلاثاء الساعة 18:00.
يرى العميل الفاتورة الجديدة مع سطر الضريبة، لكن باللون القديم والشعار القديم، وقد يظهر سطر الضريبة بلا تنسيقاته الجديدة. العيب: styles.css وlogo.png بلا بصمة، فيحصل المستخدمون العائدون على الصفحة الجديدة بتنسيقات الإصدار السابق مدةً تصل إلى 7 أيام، وبالشعار القديم مدةً تصل إلى يوم واحد. واختيار الإصلاح، كإضافة بصمة إلى الملفين، يعود إلى المطورين.
التأكيد: في ملف تعريف حمّل الفاتورة قبل الإصدار، افتحها بشكل عادي واقرأ عمود Size الذي يُظهر (disk cache) للملفين؛ ثم افتح الصفحة في نافذة خاصة حيث يظهر اللون والشعار الجديدان. هذا الفرق يُثبت أن السبب هو الذاكرة المؤقتة لا الإصدار.
أسئلة شائعة
ما الفرق بين إعادة التحميل القسرية ومسح الذاكرة المؤقتة؟
إعادة التحميل القسرية تتجاهل النسخ المخزّنة في تحميل واحد: يطلب المتصفح الصفحة وملفاتها من جديد ويتجاوز عامل الخدمة، لكنه يُبقي الذاكرة المؤقتة. أما مسح الملفات المخزّنة مؤقتاً فيحذف النسخ. ولا يزيل أيٌّ منهما عامل الخدمة؛ يزيله «Clear site data» في لوحة Application أو «Unregister».
إذا أصلح مسحُ الذاكرة المؤقتة المشكلة، فهل تبقى عيباً؟
نعم. فالعملاء لا يمسحون ذاكراتهم المؤقتة، فيستمرون في رؤية الإخفاق. والعيب في إعداد التخزين المؤقت، كبصمة مفقودة أو max-age أطول مما يجب. وتتوقف خطورته على ما يُفسده المحتوى القديم: الشعار القديم عيب ثانوي (Minor)، أما السعر القديم أو الدفع الفاشل فليس كذلك.
هل يوقف «Disable cache» في أدوات المطوّرين عامل الخدمة أيضاً؟
لا. فهو يعطّل ذاكرة HTTP المؤقتة ما دامت أدوات المطوّرين مفتوحة، لكن عامل الخدمة يستطيع أن يجيب من Cache Storage. في Chrome استخدم «Bypass for network» في Application > Service workers، أو اختبر في نافذة خاصة.
المراجع
- RFC 9111: HTTP Cachingكيف تحدد Cache-Control وExpires وETag والمتحقِّقات ما إذا كان يمكن تخزين الاستجابة وإعادة استخدامها.
- HTTP caching — MDNالتخزين المؤقت الخاص مقابل المشترك، والحداثة، والتحقق عبر ETag / Last-Modified، وأنماط Cache-Control الشائعة.
- Service Worker API — MDNعمّال الخدمة (Service Workers): سكربتات تقف بين تطبيق الويب والشبكة لاعتراض الطلبات وإدارة التخزين المؤقت وإبقاء التطبيق عاملاً دون اتصال.
تدرّب بأدوات متقن
تحليل ترويسات Cache-Control وExpires وETag وLast-Modified وVary وAge، وشرح كيفية تخزين المتصفحات وشبكات CDN للاستجابة مؤقتاً.
تحليل ترويسات طلبات واستجابات HTTP الخام إلى جدول منظّم مع الشروحات.
تكوين ترويسات HTTP باستخدام إعدادات مسبقة للتخزين المؤقت وCORS والأمان والتفاوض على المحتوى.
قارن نصّين سطراً بسطر أو كلمةً بكلمة مع إبراز الاختلافات.
للتعمّق أكثر
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.