Cache-Control وETag: مين بيتحكم في إيه داخل الكاش؟

مكعبات مضيئة تحمل بصمات فريدة تحت حلقة زمنية زرقاء، تمثيل لآلية Cache-Control وETag في التخزين المؤقت

افتح تبويب Network في المتصفح على أي موقع ووردبريس، وهتلاقي Cache-Control وETag قاعدين في ترويسات كل صورة وملف CSS تقريبًا. أغلبنا يعرف الاسمين، لكن قليل اللي يقدر يقولك كل واحد فيهم مسؤول عن إيه بالظبط. الفهم ده بيفرق جدًا لما تضبط الكاش على موقع عميل، أو تحاول تفهم ليه الزائر لسه شايف الصورة القديمة.

المشكلة التي تحلها هاتان الترويستان

التخزين المؤقت (Cache) في HTTP يسمح للمتصفح، أو لشبكة توصيل المحتوى (CDN) الواقعة في المنتصف، بالاحتفاظ بملف سبق تنزيله وإعادة استخدامه عند طلب الرابط نفسه مرة أخرى. النتيجة: طلبات أقل للسيرفر، وتحميل أسرع للزائر.

إذا لم يحدد السيرفر أي تعليمات، يضطر المتصفح إلى التخمين، والتخمين يختلف من متصفح لآخر. هنا يأتي دور الترويستين، لكن كل واحدة تجيب عن سؤال مختلف:

الترويسةالسؤال الذي تجيب عنه
Cache-Controlإلى متى يمكنك تجاهل السيرفر تمامًا؟
——
ETagبعد انتهاء هذه المدة، كيف تتأكد بتكلفة قليلة أن الملف لم يتغير؟

ماذا يتحكم فيه Cache-Control؟

Cache-Control يحدد نافذة صلاحية. في المثال الذي بنى عليه كاتب المقال الأصلي، يوجد في قالب مدونته ملف ogp-generator.php يولّد صورة المعاينة الاجتماعية (OGP) باستخدام مكتبة GD في PHP لأي ووردبريس للمحتوى المؤرخ مثل التدوينات والأخبار والشروحات. تُرتّب زمنيًا وتُنظَّم بالتصنيفات والوسوم وتظهر في الأرشيف وخلاصات RSS، بخلاف الصفحات الثابتة.">مقالة لا تملك صورة بارزة. عند إرسال الصورة الناتجة يضيف هذه الترويسات:

header('Content-Type: image/png');
header('Content-Length: ' . filesize($file));
header('Cache-Control: public, max-age=2592000, immutable');

كل توجيه هنا له وظيفة مستقلة:

  • public: يسمح بتخزين الرد في أي كاش مشترك على الطريق، مثل Cloudflare، وليس في متصفح الزائر فقط. المحتوى الخاص بمستخدم بعينه، مثل لوحة حساب العميل في ووكومرس (WooCommerce)، يستخدم private بدلًا منه.
  • max-age=2592000: عدد الثواني التي تبقى فيها النسخة صالحة، وهو هنا 30 يومًا بالضبط. طوال هذه المدة لا يتصل المتصفح بالسيرفر إطلاقًا عند طلب الرابط نفسه.
  • immutable: تعهّد بأن المحتوى لن يتغير خلال مدة بالدالة current_user_can قبل تنفيذ أي إجراء حساس مثل تعديل الحجوزات أو تسجيل الحضور.">الصلاحية. مع هذا التوجيه يتجاهل المتصفح حتى خطوة التحقق الخفيفة عند إعادة تحميل الصفحة.

الجمع بين 30 يومًا وimmutable آمن بشرط واحد: أن الرابط نفسه لن يشير أبدًا إلى محتوى مختلف. كيف تضمن ذلك؟ هذا ما سنصل إليه بعد قليل.

ماذا يتحكم فيه ETag؟ التحقق بعد انتهاء الصلاحية

عند انتهاء max-age لا يحذف المتصفح نسخته، بل يسأل السيرفر إن كانت ما زالت صالحة. ترويسة ETag (اختصار الباندويدث ويسرّع تحميل الصفحة، وتظهر فائدته بوضوح لزوار باقات الموبايل المحدودة.">304 إن لم يتغير…">Entity Tag) هي ما يجعل هذا السؤال رخيصًا.

الـ ETag معرّف قصير، غالبًا بصمة (Hash) محسوبة من محتوى الملف. لو تغيّر بايت واحد في الملف تتغير البصمة معه. خطوات إعادة التحقق كالتالي:

  1. يرسل السيرفر في الرد الأول قيمة مثل ETag: "abc123".
  2. يحفظ المتصفح القيمة، وبعد انتهاء الصلاحية يرسل طلبًا يحمل If-None-Match: "abc123".
  3. يحسب السيرفر البصمة الحالية للمحتوى. إن لم تتغير يرد بـ 304 Not Modified دون أي محتوى.
  4. يستمر المتصفح في استخدام نسخته المخزنة.

النقطة المهمة أن رد 304 لا يعيد إرسال الملف. السيرفر ما زال يبذل جهدًا لحساب البصمة، لكن نقل البيانات نفسه يُلغى. وهذا فرق ملموس لزائر يتصفح من باقة موبايل محدودة في القاهرة أو الرياض.

باختصار: max-age أداة غليظة تقول “لا تسأل طوال هذه المدة”، وETag أداة دقيقة تقول “حتى بعد انتهاء المدة، لا تُعِد التنزيل إن لم يتغير شيء”.

لماذا لا يستخدم ogp-generator.php الـ ETag؟

قد يبدو أن الملف ينقصه ETag، لكن الكود يكشف العكس: التصميم نفسه يلغي الحاجة إلى إعادة التحقق.

$modkey  = get_post_modified_time('YmdHis', true, $post); // GMT-based
$cache_file = $cache_dir . "/post-{$post_id}-{$lang}-{$modkey}.png";

اسم الملف يحمل رقم المقالة واللغة وتوقيت آخر تعديل. عندما تُعدَّل المقالة ويتغير post_modified، يتغير اسم الصورة ومن ثم رابطها. هذا الأسلوب يسمى كسر الكاش (Cache Busting): بدل أن تسأل “هل تغيّر محتوى هذا الرابط؟” تضمن أن محتوى الرابط لا يتغير أبدًا، لأن أي تعديل حقيقي ينتج رابطًا جديدًا.

بهذا التصميم لا يوجد ما يتحقق منه ETag، ويصبح immutable صادقًا تمامًا. لو أعاد المولّد استخدام اسم الملف نفسه بعد تغيير عنوان المقالة، لكان immutable كذبة، وسيرى المحررون صورة المعاينة القديمة لمدة قد تصل إلى 30 يومًا بعد كل تعديل.

الأسلوبان: وجهان لحل مشكلة واحدة

المعيارETag مع رابط ثابترقم الإصدار داخل الرابط
الرابطثابت لا يتغيريتغير مع كل تعديل
———
تكلفة كل دورة صلاحيةطلب تحقق صغير ورد 304لا شيء
يناسبمحتوى يتغير بلا إشارة واضحةمحتوى له إشارة تعديل موثوقة
مع immutableغير مناسبآمن

الاختيار بينهما يعتمد على عدد مرات تغيّر المحتوى، وعلى وجود إشارة موثوقة (مثل post_modified) يمكن بناء الإصدار عليها.

كيف تطبق الفكرة في ووردبريس؟

ووردبريس (WordPress) يطبق كسر الكاش افتراضيًا على ملفات CSS وJavaScript عبر المعامل ?ver= في الرابط. المشكلة أن كثيرًا من القوالب الفرعية (Child Theme) تمرر رقم إصدار ثابتًا، فلا يرى الزوار تعديلاتك إلا بعد مسح الكاش. الحل أن تربط الإصدار بتوقيت تعديل الملف:

add_action( 'wp_enqueue_scripts', function () {
    $path = get_stylesheet_directory() . '/assets/custom.css';
    wp_enqueue_style(
        'child-custom',
        get_stylesheet_directory_uri() . '/assets/custom.css',
        array(),
        filemtime( $path ) // version changes whenever the file changes
    );
} );

بعد ذلك يمكنك إعطاء ملفات assets صلاحية طويلة مع immutable بأمان، لأن أي تعديل سيغيّر الرابط.

في Elementor: يولّد Elementor ملفات CSS لكل صفحة داخل uploads/elementor/css/ ويضيف لها رقم إصدار. إذا ظهر تصميم قديم بعد التعديل، استخدم Elementor ← Tools ← Regenerate CSS & Data (إعادة توليد ملفات CSS)، ثم امسح كاش Cloudflare إن كنت تستخدمه.

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

الأسئلة الشائعة

س: ما الفرق بين Cache-Control وETag باختصار؟
ج: Cache-Control يحدد المدة التي يستخدم فيها المتصفح النسخة المخزنة دون سؤال السيرفر، أما ETag فيتيح التحقق السريع من حداثة النسخة بعد انتهاء تلك المدة.

س: هل أحتاج ETag إذا كنت أستخدم رقم إصدار في رابط الملف؟
ج: غالبًا لا، لأن الرابط الجديد يضمن أن المتصفح سيطلب الملف الجديد تلقائيًا، فيمكنك الاعتماد على max-age طويل مع immutable.

س: لماذا يرى الزوار النسخة القديمة من ملف CSS في ووردبريس؟
ج: لأن رقم الإصدار في الرابط لم يتغير، فالمتصفح يعتبر نسخته صالحة. اربط الإصدار بـ filemtime() أو غيّره يدويًا مع كل تعديل.

س: متى أستخدم private بدل public؟
ج: عندما يحتوي الرد على بيانات خاصة بمستخدم بعينه، مثل صفحة حسابي أو سلة التسوق، حتى لا تخزنه شبكة CDN وتعرضه لزائر آخر.

الخلاصة

Cache-Control بيحدد إمتى المتصفح يسيب السيرفر في حاله، وETag بيخلي السؤال بعد كده رخيص. ولو قدرت تحط رقم إصدار في الرابط نفسه، هتستغنى عن السؤال خالص وتدي ملفاتك صلاحية طويلة وأنت مطمّن. جرّب النهارده تربط إصدار ملفات القالب الفرعي بـ filemtime()، وابن موقعك قطعة قطعة.

مرجع المقال (بالإنجليزية): Cache-Control vs. ETag: What Each One Actually Controls
المقال ده مش ترجمة حرفية: متكيّف للقارئ العربي ومضاف عليه سياق ووردبريس وElementor وJetEngine.

اترك تعليقاً