Category: reference
HTTP Caching — Cache-Control, ETag, Stale-While-Revalidate
ทำความเข้าใจ HTTP caching headers ที่ถูกต้อง ลด server load และเร็วขึ้นสำหรับผู้ใช้กลับมา
สารบัญ
ทำไม Caching สำคัญ
Response ที่ cache ได้ = ไม่ต้อง round trip ไปหา server ทุกครั้ง:
- ลด latency ผู้ใช้รับ response จาก cache (local/CDN) แทน origin server
- ลด bandwidth ไม่ส่งข้อมูลซ้ำที่ยังไม่เปลี่ยน
- ลด server load origin server ตอบน้อยลง
Cache-Control Header
Header หลักที่ควบคุม caching behavior:
Cache-Control: max-age=3600
Directives ที่สำคัญ
| Directive | ความหมาย |
|---|---|
max-age=N | cache ได้ N วินาที |
no-cache | cache ได้ แต่ต้อง revalidate กับ server ก่อนใช้ |
no-store | ห้าม cache เลย (sensitive data) |
public | cache ได้โดย browser และ CDN |
private | cache ได้เฉพาะ browser ห้าม CDN |
immutable | บอกว่า resource จะไม่เปลี่ยนตลอด max-age |
stale-while-revalidate=N | ใช้ cache เก่าได้ระหว่าง revalidate ใน background |
must-revalidate | ต้อง revalidate เมื่อ stale |
Pattern: Static Assets (JS/CSS/Images)
Cache-Control: public, max-age=31536000, immutable
365 วัน + immutable — เหมาะกับไฟล์ที่มี content hash ในชื่อ เช่น:
main.abc123.jsstyle.def456.css
เมื่อ content เปลี่ยน filename ก็เปลี่ยน (cache bust อัตโนมัติ) ดังนั้น immutable บอกว่าไฟล์ชื่อนี้ไม่มีทางเปลี่ยน ไม่ต้อง revalidate
Pattern: HTML Files
Cache-Control: no-cache
no-cache — browser เก็บ cache ได้แต่ต้อง check กับ server ก่อนใช้เสมอ browser ส่ง If-None-Match หรือ If-Modified-Since — ถ้า server ตอบ 304 Not Modified browser ใช้ cache ได้โดยไม่ต้อง download ซ้ำ
ETag (Entity Tag)
Server ส่ง ETag = fingerprint ของ content:
HTTP/1.1 200 OK
ETag: "abc123"
Cache-Control: no-cache
Browser ส่งกลับใน request ถัดไป:
GET /page.html
If-None-Match: "abc123"
Server ตอบ:
- 304 Not Modified (content ยังเหมือนเดิม) — browser ใช้ cache
- 200 OK + content ใหม่ (content เปลี่ยน) — browser อัปเดต cache
ประหยัดแค่ bandwidth ไม่ใช่ round trip — แต่ก็ดีกว่าไม่มีอะไรเลย
Last-Modified
ทางเลือก ETag ที่ใช้เวลาแทน fingerprint:
Last-Modified: Wed, 14 Jun 2026 08:00:00 GMT
Cache-Control: no-cache
Browser ส่ง:
If-Modified-Since: Wed, 14 Jun 2026 08:00:00 GMT
Server ตอบ 304 ถ้า resource ไม่เปลี่ยนหลังจากเวลานั้น ใช้ ETag ถ้าเลือกได้ เพราะ accurate กว่า (second-precision vs content-level)
Stale-While-Revalidate
Pattern ที่ดีมากสำหรับ API ที่ข้อมูลไม่ต้อง real-time:
Cache-Control: max-age=60, stale-while-revalidate=86400
ความหมาย:
- 0–60 วินาที: ใช้ cache ได้เลย (fresh)
- 60–86460 วินาที: ใช้ cache เก่า (stale) แต่ fetch ใหม่ใน background
- หลัง 86460 วินาที: ต้องรอ fresh response
ผลลัพธ์: ผู้ใช้เห็น response เร็วเสมอ (ไม่มี loading ให้รอ) และข้อมูล fresh ภายใน request ถัดไป
Vary Header
บอก cache ว่า response อาจต่างกันตาม request header:
Vary: Accept-Encoding
Cache ต้องเก็บ response แยกกันตาม Accept-Encoding — ไม่ serve gzip content ให้ client ที่ไม่รองรับ
Vary: Accept-Language
ระวัง Vary: * — บอกว่าห้าม cache shared proxy เลย
CDN vs Browser Cache
| Browser Cache | CDN Cache | |
|---|---|---|
| ที่เก็บ | เครื่องผู้ใช้ | Edge server ใกล้ผู้ใช้ |
| ควบคุมด้วย | Cache-Control: private / public | Cache-Control: public, s-maxage |
| Invalidate | ผู้ใช้ clear cache เอง | Purge API ของ CDN |
ใช้ s-maxage | ไม่อ่าน | อ่าน (override max-age) |
Cache-Control: public, max-age=300, s-maxage=86400
Browser cache 5 นาที, CDN cache 24 ชั่วโมง — CDN รีเฟรชบ่อยกว่าเพราะ invalidate ผ่าน API ได้
Cache Busting Strategies
Content Hash (แนะนำ)
/assets/main.d3f4a9b.js → เปลี่ยน content = เปลี่ยนชื่อไฟล์
Query String
/style.css?v=2.1.0 → ง่าย แต่บาง proxy ไม่ cache URL ที่มี query string
Cache-Control: no-cache + ETag
HTML → no-cache (check ก่อน)
Assets → immutable (ไม่ต้อง check)
ตรวจสอบ Cache ด้วย DevTools
Chrome DevTools → Network tab:
- Size column: “(memory cache)” = จาก browser memory, “(disk cache)” = จาก disk
- Status 304: revalidated — ใช้ cache
- Status 200 (from cache): ใช้ cache โดยตรง ไม่ส่ง request
- กด Ctrl+Shift+R หรือ disable cache checkbox เพื่อ bypass
Headers ที่ไม่ควรใช้
/* เก่า ไม่ใช้แล้ว */
Pragma: no-cache
Expires: 0
/* ใช้ Cache-Control แทน */
Cache-Control: no-cache
Expires ยังรองรับ แต่ Cache-Control: max-age ดีกว่า เพราะไม่ขึ้นกับ clock sync ระหว่าง client และ server
Quick Reference
| Resource Type | แนะนำ |
|---|---|
| HTML (homepage, listings) | no-cache |
| HTML (article ที่ไม่ค่อยเปลี่ยน) | max-age=3600, stale-while-revalidate=86400 |
| JS/CSS (hashed filename) | max-age=31536000, immutable |
| Images (hashed) | max-age=31536000, immutable |
| Fonts | max-age=31536000, immutable |
| API (real-time) | no-store |
| API (semi-static) | max-age=60, stale-while-revalidate=600 |
| RSS/Sitemap | max-age=1800 |