أسرع طلب هو الذي لا يغادر المتصفح أصلاً
في كل مرة يزور فيها أحدهم موقعك، يرسل متصفحه عشرات الطلبات — صور وسكريبتات وملفات CSS وخطوط. إذا كان سيرفرك يُجيب على كل طلب من الصفر، فأنت تُضيّع قدراً هائلاً من السرعة الممكنة.
يتيح HTTP caching للمتصفحات والوسطاء تخزين الاستجابات وإعادة استخدامها. إذا طُبِّق بشكل صحيح، يمكنه تقليص أوقات تحميل الصفحة إلى النصف للزوار المتكررين، وتخفيف الضغط على سيرفرك بشكل ملحوظ، وتحسين Core Web Vitals — خاصةً LCP وFID.
لكن caching headers سهلة الخطأ. هذه المقالة تشرح كيف تعمل فعلاً، وما هي الـ directives المهمة، وكيف تبني استراتيجية caching تطبّقها الآن.
كيف يعمل HTTP Caching
حين يستقبل المتصفح استجابةً، يتحقق إن كانت تتضمن تعليمات حول مدة تخزينها. هذه التعليمات تأتي في HTTP response headers.
هناك آليتان رئيسيتان:
- Cache-Control — تخبر المتصفح (وأي caches وسيطة) بالمدة التي يُخزَّن فيها الرد وبأي شروط.
- Conditional requests (ETags وLast-Modified) — تتيح للمتصفح أن يسأل السيرفر "هل تغيّر هذا الملف منذ آخر مرة جلبته؟" ويحصل على استجابة خفيفة 304 Not Modified بدلاً من إعادة تنزيل الملف كاملاً.
تعمل هاتان الآليتان معاً. فهم كليهما هو مفتاح استراتيجية caching متينة.
Cache-Control: الـ Directives التي تهمك
يُعدّ header الـ Cache-Control أهم أداة بيدك. إليك ما تفعله كل directive فعلاً.
max-age
تُخبر max-age=N المتصفحَ باعتبار الاستجابة طازجةً لمدة N ثانية. خلال تلك المدة، يقدّم المتصفح الملف مباشرةً من cache المحلي — دون أي طلب شبكة على الإطلاق.
Cache-Control: max-age=31536000هذا يعادل سنة كاملة. استخدمه للأصول التي تحتوي على hash في اسم الملف (مثل main.a3f92b.js)، لأنه حين يتغير الملف يتغير الـ URL معه، متجاوزاً الـ cache تلقائياً.
no-cache مقابل no-store
يبدو الاثنان متشابهين لكنهما يتصرفان بشكل مختلف تماماً.
- no-cache — يمكن للمتصفح تخزين الاستجابة، لكن يجب إعادة التحقق منها مع السيرفر قبل استخدامها. هذا ما تريده في الغالب لصفحات HTML.
- no-store — يجب ألا يخزّن المتصفح الاستجابة نهائياً. احتفظ بهذا للبيانات الحساسة حقاً كاستجابات API الخاصة أو ردود البنوك.
public مقابل private
- public — يمكن لأي cache تخزين الاستجابة، بما في ذلك CDN caches المشتركة والـ reverse proxies.
- private — لا يمكن تخزينها إلا في متصفح المستخدم النهائي. استخدم هذا للاستجابات التي تحتوي على بيانات خاصة بالمستخدم.
stale-while-revalidate
هذه الـ directive مُهملة كثيراً وفعّالة للغاية. تُخبر المتصفح: قدّم النسخة القديمة المخزّنة فوراً، ثم تحقق من وجود نسخة جديدة في الخلفية.
Cache-Control: max-age=3600, stale-while-revalidate=86400الزوار يحصلون دائماً على استجابة فورية. ويتجدد الـ cache بصمت. لمعظم المواقع القائمة على المحتوى، هذا خيار افتراضي رائع للصور والخطوط.
ETags والـ Conditional Requests
حين تنتهي صلاحية ملف مُخزَّن، لا يضطر المتصفح إلى إعادة تنزيله بشكل أعمى. يمكنه إرسال conditional request يسأل فيه إن كان الملف قد تغيّر.
الـ ETag هو معرّف فريد يُعيّنه السيرفر لنسخة محددة من الملف — عادةً hash لمحتواه. يخزّنه المتصفح ويرسله مع الطلب التالي:
If-None-Match: "a3f92b84c1d"إذا لم يتغير الملف، يردّ السيرفر بـ 304 Not Modified بدون محتوى. قد تكون هذه الاستجابة 200 بايت بدلاً من 200 كيلوبايت. لصفحة بها 30 أصلاً، يتراكم هذا الفرق بسرعة.
يعمل Last-Modified بالطريقة نفسها لكنه يستخدم timestamp بدلاً من hash. الـ ETags أكثر موثوقية عموماً لأن الـ timestamps قد تتفاوت عبر عمليات إعادة تشغيل السيرفر أو البيئات المجمّعة.
استراتيجية Caching عملية حسب نوع الأصل
الخطأ الذي يقع فيه معظم المطورين هو تطبيق سياسة caching واحدة على كل شيء. الأصول المختلفة لها ترددات تغيير مختلفة جداً.
صفحات HTML
Cache-Control: no-cacheHTML يتغير كثيراً ويجب أن يكون طازجاً دائماً. استخدم no-cache حتى يُعيد المتصفح التحقق في كل زيارة — مع الاستفادة من استجابة 304 حين لا يكون هناك تغيير.
الأصول الثابتة المُعَيَّنة إصداراً (JS وCSS بـ hash في اسم الملف)
Cache-Control: public, max-age=31536000, immutableتُخبر الـ directive الـ immutable المتصفحَ أن هذا الملف لن يتغيّر أبداً على هذا الـ URL — تخطَّ إعادة التحقق كلياً، حتى عند الإعادة القسرية. هذا آمن فقط حين يتغير الـ URL نفسه كلما تغيّر الملف.
الصور التي لا تملك URLs مرتبطة بمحتواها
Cache-Control: public, max-age=604800, stale-while-revalidate=86400أسبوع من الطزاجة مع نافذة إعادة تحقق في الخلفية. اضبط القيم بناءً على مدى تكرار تغيير صورك فعلاً.
استجابات API
للبيانات العامة التي تتغير ببطء:
Cache-Control: public, max-age=60, stale-while-revalidate=300للاستجابات الخاصة بمستخدم معين، استخدم دائماً private:
Cache-Control: private, no-cacheكيف تضبط هذه الـ Headers على سيرفرك
في Apache، استخدم ملف .htaccess:
<FilesMatch "\.(js|css|woff2)$"> Header set Cache-Control "public, max-age=31536000, immutable" </FilesMatch>في Nginx، استخدم كتل location:
location ~* \.(js|css|woff2)$ { add_header Cache-Control "public, max-age=31536000, immutable"; expires 1y; }تضبط directive الـ expires في Nginx الـ header القديم Expires جنباً إلى جنب مع Cache-Control. تُقدّم المتصفحات الأولوية لـ Cache-Control حين يكون الاثنان موجودين، لكن تضمين كليهما يُحسّن التوافق مع العملاء القدامى.
كيف تقيس التأثير
قبل إجراء التغييرات وبعدها، تحقق باستخدام أدوات حقيقية:
- Chrome DevTools Network tab — انظر إلى عمود Size. تُظهر الاستجابات المُخزَّنة "(disk cache)" أو "(memory cache)" مع وقت نقل شبه معدوم.
- WebPageTest — شغّل اختبار repeat view. الفجوة بين أوقات تحميل الزيارة الأولى والزيارات المتكررة تُظهر بالضبط مدى فاعلية سياسة الـ cache.
- Lighthouse — تُنبّه عملية تدقيق "Serve static assets with an efficient cache policy" على أي شيء مُخزَّن لأقل من 30 يوماً وليس HTML.
- curl — افحص الـ headers مباشرةً من الطرفية: curl -I https://yoursite.com/main.js
يمكن لـ cache مضبوط جيداً أن يُقلّص حجم صفحة الزيارات المتكررة بنسبة 80% أو أكثر. لصفحة بحجم 2 ميغابايت، يعني هذا أن الزوار المتكررين يُنزّلون نحو 400 كيلوبايت فقط — فرق كبير على شبكات الجوال.
على جانب السيرفر، يُقلّل الـ caching أيضاً من الطلبات الواصلة إلى الأصل. حين يجلس reverse proxy أمام تطبيقك، فإن cache hit يعني أن عملية PHP أو Node أو Python لن تعمل أصلاً. هذا يُقلّص مباشرةً وقت الـ CPU وضغط الذاكرة واستعلامات قاعدة البيانات. إذا لم تكن متأكداً من أن الطلبات تصل فعلاً إلى الـ cache أو تخترق إلى الأصل، فمن المفيد أن تمتلك رؤية واضحة لتلك الأنابيب — رؤية ما هو مُخزَّن وما هو محجوب وما يصل إلى تطبيقك تُؤكّد أن طبقاتك تعمل كما يُفترض لا كما تفترض.
أخطاء شائعة يجب تفاديها
- تخزين HTML بشكل مكثف. إذا كانت الصفحة الرئيسية مُخزَّنة لمدة 24 ساعة ودفعت إصلاحاً حرجاً، لن يراه الزوار حتى تنتهي صلاحية الـ cache.
- استخدام no-store في كل مكان "للأمان". هذا يُجبر على تنزيل كامل في كل زيارة. تقوم بعمل إضافي دون أي فائدة.
- عدم تعيين إصدارات للأصول الثابتة. بدون hash أو رقم إصدار في اسم الملف، لا يمكنك استخدام قيم max-age طويلة بأمان. الزوار العائدون يستمرون في رؤية JS وCSS قديمين.
- نسيان Vary header. إذا كان سيرفرك يُعيد استجابات مختلفة بناءً على Accept-Encoding أو Accept-Language، أضف Vary header حتى تخزّن الـ caches نسخاً منفصلة لكل متغيّر — وإلا قد تُقدّم النسخة الخاطئة.
مكاسب سريعة يمكنك تطبيقها اليوم
- شغّل curl -I https://yoursite.com/style.css وتحقق من قيمة Cache-Control التي تُرسلها فعلاً.
- أضف immutable لأي أصل يتضمن اسم ملفه content hash.
- حوّل صفحات HTML من no-store إلى no-cache حتى تستفيد المتصفحات من استجابات 304.
- أضف stale-while-revalidate لقواعد الصور والخطوط للقضاء على ارتفاع التأخير الذي يحدث لحظة انتهاء صلاحية ملف مُخزَّن.
الـ caching ليس مثيراً، لكن الفائدة حقيقية وفورية. اضبط الـ headers بشكل صحيح، وسيُجرّب الزوار المتكررون موقعاً يبدو شبه فوري — حتى لو لم يعرفوا السبب أبداً.