Cache-Control — یک هدر HTTP است که قوانین کشینگ منابع را در طرف کلینت، پروکسیها و CDN با استفاده از یک مجموعه دستور تعیین میکند. به عکس هدر کهنه Expires، Cache-Control از دهها ترکیب پشتیبانی میکند: max-age زمان عمر را به ثانیه تعیین میکند، private و public دسترسی کش را مدیریت میکنند، no-cache و no-store بازرسی اجباری را انجام میدهند. به گزارش Google Web Dev (2025)، پیکربندی مناسب Cache-Control میتواند زمان بارگیری صفحات را برای بازدیدهای مجدد 50-80% کاهش دهد. این هدر را برای عملکرد برنامههای وب و موبایل حیاتی میسازد.
موارد اصلی
Cache-Control — یک هدر HTTP است که در HTTP/1.1 (RFC 7234) استاندارد شده است و به سرور اجازه میدهد مشخص کند که چگونه و چقدر کلینتها، پروکسیها و CDN میتوانند پاسخ را کش کنند. به عکس Expires (HTTP/1.0)، Cache-Control از دستورات استفاده میکند — دستورات متنی که با کاما از هم جدا میشوند: Cache-Control: public, max-age=3600, must-revalidate. این هدر کنترل دقیقی بر روی هر حلقه زنجیره کشینگ فراهم میکند.
کشینگ یکی از مکانیسمهای بنیادین عملکرد وب و نرمافزارهای موبایل است. بدون آن، هر درخواست کاربر مستقیماً به سرور میرفت و بار اضافی و تأخیر ایجاد میکرد. Cache-Control سه سطح کشینگ را تعیین میکند: مرورگر/برنامه (private cache)، پروکسیها (shared cache) و CDN (distributed cache). هر سطح دستورات را به شکل خود تفسیر میکند.
تنظیم نادرست Cache-Control یکی از شایعترین علل مشکلات عملکرد است. کشینگ خیلی مناسب باعث میشود کاربران دادههای کهنه را مشاهده کنند. کشینگ خیلی ضعیف منجر به درخواستهای اضافی به سرور و بارگیری کند میشود. به گزارش Akamai (2025)، بهینهسازی Cache-Control برای محتوای استاتیک بار سرور را 70-90% کاهش و زمان بارگیری را برای کاربران موبایل 40-60% بهبود میبخشد.
Cache-Control در HTTP/1.1 (RFC 2616, 1999) به عنوان جایگزین Expires ظاهر شد. Expires یک مشکل اساسی داشت: از تاریخ مطلق استفاده میکرد که به منطقه زمانی سرور و کلینت بستگی داشت. Cache-Control این مشکل را با تغییر به زمان نسبی (max-age به ثانیه از لحظه دریافت پاسخ) حل کرد. بعدها در RFC 7234 (2014) دستورات جدیدی اضافه شد: immutable برای محتوای استاتیک، stale-while-revalidate و stale-if-error برای بازرسی با تأخیر.
Cache-Control شامل بیش از 10 دستور است که به سه گروه تقسیم میشوند: دستورات درخواست (کلینت → سرور)، دستورات پاسخ (سرور → کلینت) و افزایشها. در عمل، در توسعه موبایل از 6-7 دستور پاسخ اساسی استفاده میشود که 95% سناریوهای کشینگ را پوشش میدهند. هر یک را با مثال و توصیه بررسی میکنیم.
| دستور | معنا | مثال |
|---|---|---|
| max-age | زمان عمر به ثانیه از لحظه پاسخ | max-age=3600 — 1 ساعت |
| s-maxage | max-age برای shared cache (پروکسی، CDN) | s-maxage=86400 — 1 روز برای CDN |
| public | اجازه کشینگ به همه (شامل پروکسی) | public, max-age=3600 |
| private | کش فقط برای مرورگر/برنامه | private, max-age=600 |
| no-cache | بدون بازرسی استفاده نکنید (304 اجباری) | no-cache |
| no-store | ممنوعیت کامل کشینگ | no-store |
| must-revalidate | پس از max-age باید از origin بازرسی شود | max-age=3600, must-revalidate |
| immutable | منبع تغییر نمیکند (برای استاتیک نسخهبندی شده) | max-age=31536000, immutable |
max-age — مهمترین دستور است. آن دسترسی کلینت را به سرور برای مدت زمان مشخصی ممنوع میکند. برای محتوای استاتیک (CSS، JS، تصاویر) max-age معمولاً از 1 روز تا 1 سال تنظیم میشود. برای پاسخهای API — از 0 ثانیه (دادههای همیشه تازه) تا 5-10 دقیقه (دادههای مرجع). s-maxage به شما اجازه میدهد زمان عمر متفاوتی برای CDN و مرورگر تعیین کنید: CDN یک روز و مرورگر یک ساعت کپی را نگه میدارد.
این دو دستور اغلب اشتباه گرفته میشوند. no-cache کشینگ را ممنوع نمیکند — آن از طریق یک درخواست شرطی (If-Modified-Since یا If-None-Match) در هر استفاده از کپی کششده بازرسی را میخواهد. اگر سرور 304 پاسخ دهد — کلینت از کش استفاده میکند. اگر 200 — بهروز میکند. no-store اما ذخیره پاسخ را در هر گونه کشی از جمله دیسک و حافظه به طور کامل ممنوع میکند. از no-store فقط برای دادههای حساس استفاده کنید — توکنها، اطلاعات پرداخت، اسناد شخصی.
هدر Expires (HTTP/1.0) نیز زمان عمر منبع را مشخص میکند اما از تاریخ مطلق استفاده میکند: Expires: Thu, 03 Jul 2026 12:00:00 GMT. Cache-Control max-age — زمان نسبی از لحظه پاسخ. این تفاوت برای سیستمهای توزیعشده حیاتی است: اگر سرور و کلینت در منطقه زمانی مختلفی هستند، Expires میتواند به اشتباه تفسیر شود. Cache-Control این مشکل را ندارد — 3600 ثانیه همیشه 3600 ثانیه است.
وقتی هر دو هدر موجود باشند، Cache-Control بر Expires اولویت دارد. این در RFC 7234 مشخص شده است: «اگر پاسخ شامل Cache-Control با دستور max-age باشد، دریافتکننده باید Expires را نادیده بگیرد». در عمل برای کلینتهای مدرن توصیه میشود Expires را برگردانید، زیرا Cache-Control همه سناریوهای Expires را پوشش میدهد. اما برای سازگاری با پروکسیها و مرورگرهای قدیمی میتوان هر دو هدر را برگرداند.
Expires اساساً برای محتوای استاتیک در Nginx و Apache باقی مانده است — این سرورها به طور خودکار هر دو هدر را اضافه میکنند. اگر در پروژه شما Expires بدون Cache-Control وجود دارد، آن را با Cache-Control با max-age جایگزین کنید: دقت مدیریت کش افزایش مییابد و وابستگی به منطقه زمانی برطرف میشود. برای مهاجرت کافی است سرور را به گونهای پیکربندی کنید که Cache-Control را به جای Expires اضافه کند.
# Nginx: Cache-Control برای فایلهای استاتیک
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, immutable, max-age=2592000";
}
# سیاستهای مختلف برای انواع مختلف محتوا
location /api/config {
expires -1;
add_header Cache-Control "no-cache, must-revalidate";
}
location /api/static-data {
expires 5m;
add_header Cache-Control "public, max-age=300";
}
در پیکربندی Nginx برای فایلهای استاتیک (CSS، JS، تصاویر) Cache-Control به مدت 30 روز با ویژگی immutable قرار داده میشود — این ویژگی به مرورگر اطلاع میدهد که منبع هرگز تحت این URL تغییر نمیکند (نسخهبندی از طریق هش در نام فایل). اندپوینتهای API برای دادههای دینامیک از no-cache و برای مرجع از public با max-age کوتاه استفاده میکنند — سیاههایی که مکرراً درخواست میشوند و به ندرت تغییر میکنند.
در نرمافزارهای موبایل Cache-Control نقش ویژهای ایفا میکند چراکه محدودیتهای شبکه موبایل: تأخیر بالا، اتصال ناپایدار، محدودیت ترافیک. کشینگ مناسب به کاربر اجازه میدهد دادهها را فوراً، حتی آفلاین مشاهده کند و آنها را در پسزمینه بهروز کند. OkHttp در Android و URLSession در iOS دارای سیستمهای کشینگ ساختهشدهای هستند که Cache-Control را در نظر میگیرند.
OkHttp از CacheInterceptor استفاده میکند که Cache-Control را از پاسخ میخواند و به طور خودکار کشینگ را مدیریت میکند. اگر سرور Cache-Control: max-age=3600 را برگرداند، OkHttp به مدت یک ساعت درخواستی به سرور ارسال نمیکند. پس از انقضای max-age، OkHttp یک درخواست شرطی با If-Modified-Since و If-None-Match ارسال میکند. تنظیم کش در OkHttp: OkHttpClient.Builder().cache(Cache(directory, maxSize)).
fun createCachedClient(cacheDir: File): OkHttpClient {
return OkHttpClient.Builder()
.cache(Cache(cacheDir, 10L * 1024 * 1024))
.addNetworkInterceptor { chain ->
val response = chain.proceed(chain.request())
response.newBuilder()
.header("Cache-Control",
"public, max-age=300")
.removeHeader("Pragma")
.build()
}
.build()
}
کد یک OkHttpClient با کش 10 مگبایت ایجاد میکند و Cache-Control را از طریق NetworkInterceptor لغو میکند. اگر سرور Cache-Control را برنگرداند یا از Expires استفاده کند، اینترسپتور public, max-age=300 (5 دقیقه) را اضافه میکند. اینترسپتور هدر کهنه Pragma (HTTP/1.0) را برای سازگاری حذف میکند. به همین طریق، کشینگ در iOS از طریق URLCache.shared با تنظیم memoryCapacity و diskCapacity کار میکند.
دستور stale-while-revalidate به کاربر اجازه میدهد کش کهنه (stale) را مشاهده کند در حالی که برنامه در پسزمینه دادههای تازه را بارگیری میکند. این اثر پاسخ فوری را ایجاد میکند: کاربر محتوا را فوراً میبیند و پس از یک ثانیه بهروز میشود. توسط OkHttp از نسخه 3.10 و URLCache در iOS 14+ پشتیبانی میشود. مثال: Cache-Control: max-age=3600, stale-while-revalidate=300 — 1 ساعت کش فعال، سپس 5 دقیقه نمایش کش کهنه با بهروزرسانی پسزمینه.
انواع مختلف منابع به راهبردهای کشینگ متفاوتی نیاز دارند. پیکربندیهای بهینه را برای سناریوهای معمولی در توسعه موبایل بررسی میکنیم. برای محتوای استاتیک با هش در نام فایل (bundle.abc123.js) میتوان max-age را تا 1 سال با immutable تنظیم کرد. برای لیستهای API که به ندرت بهروز میشوند (کاتالوگها، دستهبندیها) — max-age از 5 دقیقه تا 1 ساعت با stale-while-revalidate.
| نوع منبع | Cache-Control | توضیح |
|---|---|---|
| استاتیک نسخهبندیشده | public, max-age=31536000, immutable | 1 سال، فایلها تغییر نمیکنند (هش در URL) |
| استاتیک نسخهبندی نشده | public, max-age=86400, must-revalidate | 1 روز با بازرسی اجباری پس از |
| API: دادههای مرجع | public, max-age=600, stale-while-revalidate=60 | 10 دقیقه کش + 1 دقیقه stale |
| API: دادههای کاربری | private, max-age=60 | 1 دقیقه، فقط برای کاربر مشخص |
| API: دادههای حساس | no-store | ممنوعیت کامل کشینگ |
| صفحات HTML | no-cache, must-revalidate | بازرسی در هر درخواست، 304 در صورت بدون تغییر |
مهم است در مورد امنیت به یاد داشته باشید: برای پاسخهایی که شامل دادههای شخصی کاربر هستند، همیشه private تنظیم کنید. بدون این دستور، پروکسی عمومی (مثل سازمانی) میتواند پاسخ را کش کند و آن را به کاربر دیگری بدهد. برای توکنهای احراز هویت و اطلاعات پرداخت از no-store استفاده کنید — حتی کش private هم نباید این دادهها را روی دیسک ذخیره کند.
برای بررسی صحت Cache-Control از هدر Age (چقدر ثانیه کش ذخیره شده) و X-Cache (hit/miss در CDN) استفاده کنید. در مرورگر — زبانه Network، ستون Size «from disk cache» یا «304 Not Modified» را نشان میدهد. اگر منبع باید کش شود اما هر بار بارگیری میشود، بررسی کنید که آیا سرور Cache-Control: no-cache یا Pragma: no-cache را همراه با دستورات شما اضافه نمیکند.
سوالات متداول
max-age برای همه کشها (شامل مرورگرها) است، s-maxage فقط برای shared cache (پروکسی و CDN). اگر s-maxage مشخص شده باشد، CDN max-age را نادیده گرفته و از s-maxage استفاده میکند. این امکان را میدهد زمان عمر متفاوتی برای مرورگر و CDN تعیین کنید.
خیر، پس از ارسال پاسخ با max-age، کلینت تا پایان تایمر درخواستی ارسال نخواهد کرد. برای ابطال فوری کش باید URL منبع را تغییر دهید (نسخه/هش اضافه کنید) و اعلامهای push یا پیامهای WebSocket برای بازنشانی اجباری ارسال کنید.
دستور immutable (RFC 8246) به مرورگر اطلاع میدهد که منبع هرگز تحت این URL تغییر نخواهد کرد. مرورگر حتی در بازگشت صفحه درخواست شرطی نمیفرستد — تا پایان max-age از کش استفاده میکند. فقط با فایلهای نسخهبندی شده کار میکند.
Googlebot Cache-Control را در نظر میگیرد: کشینگ طولانی اسکن مجدد را سریعتر میکند. noindex با کش سریع — مناسب است. no-store میتواند فهرستبندی را کند کند، زیرا Googlebot صفحه را هر بار از صفر بارگیری خواهد کرد. max-age خیلی کوتاه بار سرور را در زمان اسکن افزایش میدهد.
از طریق helmet یا middleware: res.set('Cache-Control', 'public, max-age=3600'). برای محتوای استاتیک از express.static با پارامتر maxAge استفاده کنید: express.static('public', {maxAge: '1y'}). برای مسیرهای دینامیک — به صورت جداگانه در هر handler.
نتایج
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید