من الذي يصغي إلى هذا الرأس
تمرّ الاستجابة الواحدة عبر عدة ذواكر مؤقتة: متصفح الزائر (خاصة — تخدم شخصًا واحدًا)، وedge الـ CDN (مشتركة — تخدم الجميع)، وأحيانًا وسيط بينهما. وCache-Control هو كيف يخاطب الخادم الأصلي هذه جميعًا دفعةً واحدة، ولهذا وُجدت توجيهات تخاطب كلًّا منها على حدة.
هذا التمييز يقود كل ما عداه. فلوحة تحكّم لمستخدم مسجَّل الدخول قد تكون قابلة للتخزين في متصفح ذلك المستخدم، ويجب ألّا تُخزَّن أبدًا على الـ edge حيث قد يتلقّاها مستخدم آخر. والصفحة التسويقية العامة على العكس: خزّنها بقوة على الـ edge، ولوقت قصير في المتصفح. وحسم "خاصة أم مشتركة" أولًا يجعل كل خيار آخر بديهيًا.
التوجيهات التي تهمّ فعلًا
max-age=N — قابلة لإعادة الاستخدام N ثانية دون سؤال مجدّدًا. وهي المقبض الرئيسي.
s-maxage=N — الشيء نفسه، لكن للذواكر المشتركة فقط (الـ CDN). وحين توجد، يطيعها الـ edge ويتجاهل max-age، فيمكنك إبقاء شيء على الـ edge لساعة بينما تحتفظ به المتصفحات دقيقة واحدة.
public / private — public يجوز لأي ذاكرة مؤقتة تخزينها؛ وprivate تقصر التخزين على متصفح الفرد. وكل ما يخصّ مستخدمًا بعينه يجب أن يكون private.
no-cache — خزّنها، لكن أعد التحقّق مع الخادم الأصلي قبل كل إعادة استخدام. وهي رخيصة حين لا يتغيّر شيء: إذ يمكن للخادم أن يجيب 304 Not Modified دون متن.
no-store — لا تكتبها أبدًا في أي ذاكرة مؤقتة، لا في الذاكرة ولا على القرص. احتفظ بها للاستجابات الحسّاسة فعلًا؛ فهي ليست "من فضلك كن حديثًا"، بل "لا تحتفظ بأي نسخة إطلاقًا".
immutable — لن يتغيّر هذا المتن على هذا الرابط أبدًا، فلا تُعِد التحقّق حتى عند إعادة التحميل. وهي صادقة فقط مع أسماء الملفات المُرقّمة.
stale-while-revalidate=N — بعد انتهاء الصلاحية، استمر في تقديم النسخة القديمة حتى N ثانية بينما تُجلب نسخة جديدة في الخلفية. فلا ينتظر الزائر إعادة الجلب أبدًا.
ثلاث وصفات تغطي معظم المواقع
الأصول الثابتة المُرقّمة — يتغيّر اسم الملف كلما تغيّر محتواه، فالرابط آمن للتخزين إلى الأبد. وهذا أكبر مكسب تخزين مؤقت متاح وأكثره أمانًا.
صفحات HTML — يبقى الرابط نفسه بينما يتغيّر المحتوى، فتحتاج عمرًا قصيرًا. وmax-age وجيز مع stale-while-revalidate يمنحك السرعة دون تقديم صفحة الأمس.
الاستجابات الخاصة أو المخصّصة — لوحات التحكّم، وسلال الشراء، وكل ما خلف تسجيل دخول. أبقِها خارج الذواكر المشتركة؛ ويجوز للمتصفح الاحتفاظ بها لوقت قصير إن كان ذلك آمنًا بالنسبة لك.
نقاط انطلاق جاهزة للنسخ — اضبط الأرقام حسب وتيرة إصداراتك
# versioned assets: /js/app.a1b2c3.js
Cache-Control: public, max-age=31536000, immutable
# HTML pages (short at the browser, longer at the edge, no waiting on refresh)
Cache-Control: public, max-age=60, s-maxage=600, stale-while-revalidate=86400
# per-user pages: never at the edge
Cache-Control: private, no-store
# an API response that changes often but can lag a little
Cache-Control: public, max-age=0, s-maxage=30, stale-while-revalidate=60
no-cache مقابل no-store: الخطأ الذي يستحق التجنّب
تبدوان مترادفتين ولا تتصرّفان بالطريقة نفسها إطلاقًا. no-cache تسمح بالتخزين لكنها تشترط إعادة التحقّق قبل إعادة الاستخدام — فتبقى النسخة، وحين تكون ما زالت حديثة يردّ الخادم 304 دون متن، وهو أمر رخيص جدًا. أما no-store فتمنع الاحتفاظ بالاستجابة في أي مكان.
واللجوء إلى no-store وأنت تقصد no-cache يرمي كل تحسين دون أي فائدة: إذ يصبح كل طلب تنزيلًا كاملًا حتى حين لا يتغيّر شيء. استخدم no-store فقط حين تكون النسخة المخزَّنة نفسها هي المشكلة — كشوف الحسابات المصرفية، وصفحات إعادة تعيين كلمة المرور، وكل ما يجب ألّا يبقى في ذاكرة جهاز مشترك. أما لـ"أظهر الأحدث دائمًا"، فـ no-cache هي الإجابة الصحيحة والأرخص بكثير.
كيف يتفاعل هذا مع شبكتك
الرؤوس التي يرسلها خادمك الأصلي هي ما يطيعه الـ edge حين يقرّر كم يحتفظ بالنسخة — وهذا ما يجعل Cache-Control سطح التحكّم بسلسلة التوصيل كاملةً، لا تفصيلًا يخصّ المتصفح. أرسل s-maxage فيتّبعه الـ edge؛ وأرسل no-store فيرفض الـ edge التخزين إطلاقًا، فيسافر كل طلب إلى خادمك الأصلي وتكون قد أطفأت الـ CDN عمليًا لتلك الاستجابة.
وعلى cdn.com.tr يمكنك أيضًا ضبط سلوك التخزين المؤقت لكل قاعدة توصيل من اللوحة حين لا تستطيع تغيير رؤوس التطبيق — وهو مفيد للتطبيقات القديمة التي تفضّل عدم المساس بها. وحين تغيّر ما يعيده رابط مخزَّن، تذكّر أن الـ edge ما زال يحتفظ بالنسخة السابقة حتى تنتهي صلاحيتها: ولهذا وُجد التفريغ (purge).
تحقّق مما ترسله فعلًا
الافتراضات حول الرؤوس خاطئة كثيرًا — فغالبًا ما يتجاوز إعدادٌ افتراضي في إطار عمل أو إضافة أو خادم ويب ما تظنّ أنك ضبطته. تحقّق بأمر واحد لكل نوع رابط، وافحص أصلًا مُرقّمًا وصفحة HTML معًا، إذ يجب أن يبدوا مختلفين تمامًا. وانتبه أيضًا لوجود Set-Cookie على استجابة قصدت تخزينها بشكل عام: فكثير من الذواكر المؤقتة ترفض تخزين تلك، وهو سبب شائع لعدم تخزين صفحة ما بشكل غامض.
اقرأ رؤوس الاستجابة الحقيقية
# see what the edge and origin actually say
curl -sI https://example.com/ | grep -i "cache-control\|age\|set-cookie"
# compare a versioned asset (should be a long max-age)
curl -sI https://example.com/js/app.a1b2c3.js | grep -i cache-control
الأسئلة الشائعة
ما قيمة max-age المناسبة لصفحات HTML؟
قصيرة — من ثوانٍ إلى بضع دقائق — لأن الرابط يبقى نفسه بينما يتغيّر المحتوى. اقرنها بـ s-maxage أطول على الـ edge ومع stale-while-revalidate ليحصل الزوّار على استجابة فورية بينما يجري التحديث في الخلفية.
هل ما زال Expires مطلوبًا إلى جانب Cache-Control؟
لا. يتجاوز Cache-Control رأس Expires ويفوز عليه أينما وُجدا معًا. ولا يهمّ Expires إلا للعملاء القديمة جدًا؛ وإرساله معه غير ضارّ لكنه لا يضيف شيئًا.
لماذا لا تُخزَّن صفحتي مؤقتًا رغم max-age الطويل؟
غالبًا بسبب رأس Set-Cookie على الاستجابة، أو توجيه private أو no-store في مكان ما من السلسلة، أو معامل استعلام يجعل كل طلب مفتاح ذاكرة مؤقتة فريدًا. افحص رؤوس الاستجابة الفعلية قبل تغيير الإعدادات.
هل تعني immutable الأبد فعلًا؟
تعني "المتن على هذا الرابط لن يتغيّر"، فتتخطّى الذواكر المؤقتة إعادة التحقّق كليًا. وهذا صحيح فقط لأسماء الملفات المُرقّمة. ووضع immutable على رابط تستبدله لاحقًا هو الطريقة التي يعلق بها الزوّار على ملف قديم دون وسيلة نظيفة للإصلاح.