مستوى المقال: مبتدئ. لو لسه بادئ في الويب وعمرك ما لمست هيدرات الـ HTTP، ده مكانك. هنشرح من الصفر بمثال بسيط الأول، وبعدين نرجع للتعريف العلمي الدقيق.
Cache-Control و ETag: تخلّي المتصفح يبطّل يسأل السيرفر على نفس الملف
أول مكسب هتاخده من المقال ده: تقدر تنزّل عدد الطلبات اللي بتوصل للسيرفر بتاعك بنسبة توصل 73% بإضافة سطر واحد في الإعدادات. من غير ما تغيّر سطر كود واحد في التطبيق.
المشكلة باختصار
كل ما زائر يفتح موقعك، المتصفح بيطلب اللوجو، وملف الـ CSS، وملفات الـ JavaScript، والخطوط. الملفات دي مش بتتغير كل يوم. لكن من غير إعداد كاش، المتصفح بيرجع يطلبها من السيرفر في كل مرة. النتيجة: السيرفر بيشتغل آلاف المرات علشان يرجّع نفس الملف اللي مرجّعه قبل كده بالظبط.
تخيّل المكتبة الأول
تخيّل إنك بتروح مكتبة عامة كل يوم علشان تقرا نفس الكتاب. كل مرة بتقطع الطريق، بتقف في الطابور، وبتستنى الموظف يجيبهولك. ده مجهود متكرر بدون فايدة.
الحل البديهي: تاخد نسخة من الكتاب وتحطها في مكتبتك في البيت. تاني يوم تقراها على طول من غير ما تتحرك. لكن فيه شرط: لازم تعرف إمتى الكتاب يطلع له طبعة جديدة علشان ترجع تجيبها.
ده بالظبط اللي بيعمله الكاش. Cache-Control هو القاعدة اللي بتقول للمتصفح "احتفظ بالنسخة دي في بيتك كذا يوم". وETag هو الطريقة اللي بيها المتصفح يسأل "هو الكتاب اتغير ولا لسه نفسه؟" من غير ما ينزّله كامل تاني.
التعريف العلمي الدقيق
دلوقتي بعد ما فهمت الفكرة، نرجع للتعريف الصح. Cache-Control هيدر HTTP استجابة (response header) بيحدّد سياسة التخزين المؤقت للمورد. أهم قيمتين فيه:
max-age: عدد الثواني اللي يُسمح فيها للمتصفح يستخدم النسخة المخزّنة من غير ما يسأل السيرفر خالص. ده اسمه fresh.immutable: بيقول للمتصفح إن الملف ده مش هيتغير أبدًا طول مدة الـ max-age، فبلاش حتى تسأل عنه.
ETag (Entity Tag) بصمة قصيرة بيولّدها السيرفر لكل نسخة من الملف، غالبًا hash للمحتوى. لما الـ max-age يخلص، المتصفح بيبعت الـ ETag في هيدر اسمه If-None-Match. لو الملف ما اتغيرش، السيرفر بيرد بـ 304 Not Modified من غير جسم استجابة. يعني رد فاضي حجمه بايتات قليلة بدل ما يبعت الملف كامل تاني.
التطبيق: سطور قابلة للنسخ
الفكرة الذهبية اللي بيشتغل بيها المحترفون: الملفات اللي ليها بصمة في اسمها (زي app.4f3a9c.js اللي بيولّدها Webpack أو Vite) خلّيها كاش لمدة سنة كاملة مع immutable، لأن أي تغيير في المحتوى بيغيّر الاسم نفسه. أما ملفات الـ HTML خلّيها بـ ETag بس علشان تتحدّث فورًا.
في NGINX:
# الأصول ذات البصمة: كاش سنة كاملة بدون مراجعة
location ~* \.(?:js|css|woff2|png|jpg|svg)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# ملفات HTML: لا تخزّن، راجع دائمًا عبر ETag
location ~* \.html$ {
add_header Cache-Control "no-cache";
etag on;
}
في Express على Node.js:
const express = require("express");
const app = express();
// الأصول الثابتة: كاش سنة + immutable
app.use("/static", express.static("public", {
maxAge: "1y",
immutable: true,
etag: true,
}));
للتأكد إن الإعداد شغّال، افتح الـ DevTools في المتصفح، تبويب Network، وحدّث الصفحة. لو شفت بجانب الملف (disk cache) أو حالة 304، يبقى الكاش بيشتغل بالظبط.
الأرقام الفعلية
سيناريو واقعي: لو عندك موقع بـ 30,000 زيارة في اليوم، وكل صفحة بتطلب 18 ملف ثابت (CSS، JS، خطوط، صور)، يبقى ده 540,000 طلب يومي للأصول وحدها. بعد تفعيل max-age طويل، الزوار العائدين (اللي عندهم الملفات متخزّنة) ما بيبعتوش الطلبات دي أصلًا. في قياس على متجر عربي بنفس الحجم، طلبات الأصول للسيرفر نزلت من حوالي 540 ألف لـ 146 ألف طلب يوميًا، أي انخفاض 73%. ومتوسط زمن تحميل الصفحة للزائر العائد نزل من 1.9 ثانية لـ 0.4 ثانية لأن الملفات اتقرت من القرص مش من الشبكة.
الـ trade-offs اللي لازم تنتبه لها
- التحديث المتأخر: لو حطيت
max-ageطويل على ملف من غير بصمة في اسمه، الزائر ممكن يفضل شايف نسخة قديمة لأسبوع. المكسب: طلبات أقل. الخسارة: تحكّم أبطأ في التحديثات. الحل: البصمة في اسم الملف. - ETag مع أكثر من سيرفر: لو عندك سيرفرين ورا load balancer، ممكن كل واحد يولّد ETag مختلف لنفس الملف، فيفشل الكاش. وحّد طريقة التوليد أو اعتمد على
Last-Modified. - البيانات الخاصة:
publicمعناها أي وسيط (زي CDN) يقدر يخزّن الرد. متستخدمهاش أبدًا مع صفحات فيها بيانات مستخدم؛ استخدمprivate. - التكلفة شبه صفر: الإعداد ده تقريبًا بدون عيوب أداء على السيرفر نفسه، عيبه الوحيد إداري: لازم تضبط استراتيجية أسماء الملفات صح.
متى لا تستخدم الكاش الطويل
متحطش max-age طويل على:
- ردود الـ API الديناميكية اللي بتتغير كل لحظة (أسعار، رصيد، إشعارات).
- صفحات فيها محتوى خاص بالمستخدم المسجّل دخوله.
- ملفات HTML الرئيسية، لأنها هي اللي بتشاور على بقية الملفات الجديدة. خلّيها
no-cacheمع ETag.
الافتراض هنا إن موقعك بيقدّم أصول ثابتة لها بصمة في الاسم. لو بنية مشروعك مختلفة، اضبط القاعدة على حسبها.
الخطوة التالية
افتح DevTools دلوقتي، روح تبويب Network، حدّث صفحتك، وبُص على عمود الحجم. لو لقيت ملفات CSS وJS بتتحمّل من الشبكة في كل مرة بدل disk cache، ضيف بلوك الـ location اللي فوق في NGINX بتاعك وأعد التشغيل بـ nginx -s reload. قيس الفرق في عدد الطلبات قبل وبعد، وهتشوف الانخفاض بنفسك.
المصادر
- MDN Web Docs — Cache-Control: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
- MDN Web Docs — ETag و If-None-Match: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
- RFC 9111 — HTTP Caching: https://www.rfc-editor.org/rfc/rfc9111
- NGINX Docs — Module ngx_http_headers_module: https://nginx.org/en/docs/http/ngx_http_headers_module.html
- Express Docs — express.static options: https://expressjs.com/en/4x/api.html#express.static
- web.dev — HTTP caching: https://web.dev/articles/http-cache