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 กำหนดสามระดับแคชวิง: เบราว์เซอร์/แอปพลิเคชัน (แคชส่วนตัว), เซิร์ฟเวอร์โพรกซี (แคชร่วมใช้), และ CDN (แคชกระจาย) แต่ละระดับแปลคำสั่งแตกต่างกัน
การกำหนดค่า 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 สำหรับแคชร่วม (โพรกซี, 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 ต้องตรวจสอบกับแหล่งต้นทาง | 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 เก็บสำเนาไว้ 1 วัน, เบราว์เซอร์เก็บไว้ 1 ชั่วโมง
คำสั่งสองนี้มักถูกสับสน 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: คำสั่งควบคุมแคชสำหรับไฟล์คงที่
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 MB และแทนที่ Cache-Control ผ่าน NetworkInterceptor หากเซิร์ฟเวอร์ไม่ส่งคืน Cache-Control หรือใช้ Expires ตัวสกัดจะเพิ่ม public, max-age=300 (5 นาที) ตัวสกัดจะลบส่วนหัว Pragma (HTTP/1.0) ที่ล้าสมัยเพื่อความเข้ากันได้ แคชวิงบน iOS ทำงานคล้ายคลึงผ่าน URLCache.shared กับการตั้งค่า memoryCapacity และ diskCapacity
คำสั่ง stale-while-revalidate ช่วยให้แสดงแคชที่ล้าสมัยแก่ผู้ใช้ในขณะที่แอปพลิเคชันดึงข้อมูลใหม่ในพื้นหลัง ทำให้เกิดผลการตอบสนองทันที: ผู้ใช้เห็นเนื้อหาทันที และหลังจากหนึ่งวินาทีมันจะอัปเดตเป็นเวอร์ชันล่าสุด รองรับโดย 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 นาทีที่ล้าสมัย |
| API: ข้อมูลผู้ใช้ | private, max-age=60 | 1 นาที, เฉพาะสำหรับผู้ใช้รายใดรายหนึ่ง |
| API: ข้อมูลที่อ่อนไหว | no-store | ห้ามแคชโดยสิ้นเชิง |
| หน้า HTML | no-cache, must-revalidate | ตรวจสอบทุกคำขอ, 304 หากไม่เปลี่ยนแปลง |
สิ่งสำคัญที่ต้องจำคือ ความปลอดภัย: สำหรับคำตอบที่มีข้อมูลส่วนบุคคลของผู้ใช้ ให้ตั้ง private เสมอ หากไม่มีคำสั่งนี้ โพรกซีสาธารณะ (ตัวอย่าง องค์กร) สามารถแคชคำตอบและส่งต่อให้ผู้ใช้รายอื่น สำหรับโทเคนการตรวจสอบและข้อมูลการชำระเงิน ให้ใช้ no-store — แม้แต่แคชส่วนตัวก็ไม่ควรเก็บข้อมูลนี้ไว้บนแผ่นดิสก์
เพื่อตรวจสอบความถูกต้องของ 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 ใช้กับแคชร่วม (โพรกซี, 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'}) สำหรับเส้นทางไฮนามิก — แยกต่างหากใน แต่ละตัวจัดการ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม