Stale-While-Revalidate และ Cache-Control ปี 2026: คู่มือลด TTFB ด้วย Edge CDN Caching

คู่มือ Stale-While-Revalidate และ Cache-Control ปี 2026 ตั้งค่า edge CDN caching บน Cloudflare, Vercel, Fastly, nginx พร้อมโค้ดจริง กลยุทธ์ tag-based purge และวิธีวัดผล TTFB, LCP ด้วย RUM ที่ใช้ในโปรเจกต์จริง

SWR & Cache-Control ลด TTFB (2026)

อัปเดตล่าสุด: 16 กรกฎาคม 2026

Stale-While-Revalidate (SWR) คือคำสั่งใน Cache-Control ที่อนุญาตให้ CDN หรือเบราว์เซอร์ส่งเนื้อหาเวอร์ชันเก่า (stale) กลับให้ผู้ใช้ทันที ในขณะเดียวกันก็ยิงคำขอไปดึงเวอร์ชันใหม่มาอัปเดตแคชแบบ background การใช้ SWR ร่วมกับ max-age, s-maxage และการตั้งค่า edge CDN อย่างถูกต้องจะช่วยลด TTFB ให้เหลือหลักสิบมิลลิวินาที และผลักคะแนน LCP กับ INP ให้อยู่ในโซนสีเขียวได้ทั่วโลก ในคู่มือนี้ผมจะอธิบายทั้งกลไกภายใน HTTP header ตัวอย่างการตั้งค่าจริงบน Cloudflare, Vercel, Fastly, nginx และข้อผิดพลาดที่ผมเจอบ่อยตอน invalidate cache (บอกตรงๆ ว่า SWR คือหนึ่งใน 3 ท่าที่คุ้มค่าที่สุดที่ผมแนะนำลูกค้าทุกครั้งเวลา audit performance)

  • stale-while-revalidate=N อนุญาตให้แคชส่งเนื้อหาเก่าไปให้ผู้ใช้ก่อน แล้วดึงเวอร์ชันใหม่มาอัปเดตเบื้องหลังภายใน N วินาที ทำให้ TTFB ต่ำมากแม้แคชจะหมดอายุแล้ว
  • max-age ใช้กับเบราว์เซอร์ ส่วน s-maxage เฉพาะ shared cache (CDN, proxy) แยกกันชัดเจนได้ในปี 2026 ทำให้ปรับกลยุทธ์แคชละเอียดขึ้น
  • Cloudflare, Vercel Edge, Fastly, Akamai และ AWS CloudFront รองรับ stale-while-revalidate ตาม RFC 5861 พร้อมกลไก tag-based purge ที่ทำให้ invalidate ได้ในไม่กี่วินาที
  • การใช้ ETag + If-None-Match ช่วยประหยัด egress bandwidth และคืนสถานะ 304 Not Modified ใช้ควบคู่กับ SWR เพื่อลด revalidation cost
  • อย่าลืมตั้ง Vary: Accept-Encoding, Accept เพื่อไม่ให้ CDN แคช response ผิดคน โดยเฉพาะเมื่อเสิร์ฟทั้ง AVIF และ WebP จาก URL เดียวกัน
  • คำสั่ง stale-if-error=N เป็นตัวช่วยฉุกเฉิน หาก origin ล่ม CDN จะยังเสิร์ฟเนื้อหาเก่าให้ผู้ใช้ต่อได้ ไม่เห็นหน้า 5xx

Stale-While-Revalidate คืออะไร และทำงานอย่างไรกันแน่

stale-while-revalidate ถูกกำหนดใน RFC 5861 เพื่อขยายวิธีการทำงานของแคชแบบเดิม ที่พอ max-age หมด ก็ต้องบล็อกผู้ใช้รอ origin ตอบก่อน SWR แก้ปัญหานี้ด้วยการเพิ่ม "window" เวลาให้แคช สามารถส่ง response เก่าไปให้ผู้ใช้ทันทีในขณะที่ตัวแคชเองยิงคำขอ background ไปยัง origin เพื่อรีเฟรชเนื้อหาใหม่มาแทนที่ พอผู้ใช้คนต่อไปเข้ามา ก็จะได้เนื้อหา fresh แล้ว

ตัวอย่าง header ที่พบบ่อยในปี 2026:

Cache-Control: public, max-age=60, s-maxage=600, stale-while-revalidate=86400

แปลว่า: เบราว์เซอร์แคช 60 วินาที, CDN แคช 10 นาที, และหลังจากนั้น CDN ยังสามารถเสิร์ฟเวอร์ชันเก่าได้อีก 24 ชั่วโมงในขณะที่ยิง revalidation กลับไปที่ origin แบบ async ในทางปฏิบัติ user ที่เข้ามาช่วงหลัง 10 นาทีจะได้รับ response เร็วเท่ากับ cache hit ปกติ (TTFB < 50ms จาก edge) แต่ CDN จะอัปเดตเนื้อหาให้ผู้ใช้คนถัดไป

ผมชอบเปรียบ SWR เหมือน "ร้านกาแฟที่ชงล่วงหน้า" — ลูกค้าคนแรกที่มาถึงหลังกาแฟหมดจะได้ถ้วยเก่าไปดื่ม ในขณะที่บาริสต้ากำลังชงล็อตใหม่ ลูกค้าคนที่สองได้ล็อตใหม่แล้ว ไม่มีใครต้องรอที่เคาน์เตอร์

ทำไม stale-while-revalidate ถึงลด TTFB และดันคะแนน Core Web Vitals ได้

TTFB (Time to First Byte) คือเวลาระหว่างที่เบราว์เซอร์ส่งคำขอไปยัง server จนกระทั่งได้รับ byte แรกกลับมา หาก TTFB สูงเกิน 800ms Google จะจัดว่าอยู่ในโซน "poor" ทันที และมันส่งผลลูกโซ่ไปยัง LCP ที่ต้องได้ HTML มาก่อนจึงจะเริ่ม parse หา resource ต่างๆ ได้ ในระบบ dynamic (SSR, ISR, API route) หากไม่มีแคช TTFB จะขึ้นอยู่กับ DB query, external API และ latency ระหว่าง edge → origin ทั้งหมด

เมื่อเปิดใช้ SWR บน edge CDN cache hit ที่เป็น stale จะได้ TTFB ประมาณ 20-80ms ขึ้นอยู่กับระยะทางไปยัง POP ที่ใกล้ที่สุด เทียบกับกรณี origin request ที่อาจต้องใช้ 400-1200ms สำหรับหน้า SSR ผมเคยเห็นเว็บ e-commerce ในไทยที่ TTFB p75 ลดจาก 950ms เหลือ 62ms หลังเปิด SWR บน Cloudflare ในทันที เพราะ 92% ของ request เป็น cache hit

ผลกระทบต่อ Core Web Vitals ค่อนข้างตรงไปตรงมา: LCP มักลดตาม TTFB เกือบ 1:1 ในหน้าที่ LCP element เป็น HTML text หรือรูปที่โหลดจาก URL ตายตัว ส่วน INP ก็ได้อานิสงส์เพราะ main thread ไม่ต้องรอ hydration data นานเท่าเดิม สำหรับกลยุทธ์อื่นที่ช่วยเพิ่ม Core Web Vitals แนะนำอ่านคู่มือ HTTP 103 Early Hints เร่ง TTFB และ LCP ควบคู่กันได้ เพราะ Early Hints ใช้ได้แม้แคช miss

Cache-Control Directives ทั้งหมดที่ต้องรู้ในปี 2026

เอาจริงๆ ปี 2026 การรองรับ Cache-Control directives ในระดับ CDN และเบราว์เซอร์นิ่งขึ้นเยอะ แต่ทีมส่วนใหญ่ที่ผมเข้าไปตรวจยังใช้แค่ max-age ล้วนๆ ซึ่งเป็นการทิ้งพลังไปเปล่าประโยชน์ นี่คือ directives ที่ควรรู้จักและใช้ให้ถูกจังหวะ

  • public: บอกให้ทุก cache (เบราว์เซอร์ + shared) แคชได้ ใช้กับหน้า marketing, blog, product listing ที่เนื้อหาเหมือนกันทุกคน
  • private: เฉพาะเบราว์เซอร์ของผู้ใช้เท่านั้นที่แคชได้ CDN ห้ามแคช ใช้กับ /account, /cart หรือหน้าที่มีข้อมูลส่วนตัว
  • max-age=N: อายุแคชสำหรับ user agent (เบราว์เซอร์) เป็นวินาที
  • s-maxage=N: อายุแคชสำหรับ shared cache (CDN, reverse proxy) override max-age เมื่อทั้งคู่ปรากฏ
  • stale-while-revalidate=N: window ที่แคชส่งเวอร์ชันเก่าได้ ระหว่าง revalidate background
  • stale-if-error=N: window ฉุกเฉินให้เสิร์ฟเนื้อหาเก่าถ้า origin คืน 5xx หรือ timeout
  • must-revalidate: บังคับให้ตรวจสอบกับ origin หลัง max-age หมด (ห้ามเสิร์ฟ stale เว้นแต่ SWR อนุญาต)
  • immutable: บอกว่าเนื้อหาจะไม่เปลี่ยนตลอด lifetime ของ URL ใช้กับ hashed asset (main.abc123.js) เบราว์เซอร์จะข้าม conditional request ทันที
  • no-cache: ไม่ใช่ "ห้ามแคช" แต่แปลว่า "ต้อง revalidate ก่อนใช้ทุกครั้ง" (คล้าย ETag flow)
  • no-store: ห้ามแคชโดยเด็ดขาด ไม่มีการเก็บลง disk/memory เลย ใช้กับข้อมูลอ่อนไหวจริงๆ เช่นข้อมูลบัตรเครดิตในระหว่าง checkout

max-age vs s-maxage ต่างกันอย่างไร (พร้อมตารางเปรียบเทียบ)

คำถามที่ผมเจอบ่อยที่สุดใน code review คือ "ควรใช้ max-age หรือ s-maxage?" คำตอบสั้นๆ คือ ใช้ทั้งคู่ แต่ตั้งค่าคนละแบบตามพฤติกรรมของ audience จริงๆ ในตารางด้านล่างสรุปความต่างชัดๆ

คุณสมบัติmax-ages-maxage
ใครใช้ค่านี้เบราว์เซอร์ ผู้ใช้CDN, reverse proxy, shared cache
ค่าปกติที่แนะนำ0-60 วินาที300-31536000 วินาที
ผลต่อ TTFB คนแรกที่เข้ามาใหม่ไม่มีผลลด TTFB ทันทีเมื่อ cache hit
ต้อง purge เมื่อเนื้อหาเปลี่ยนไม่ต้อง เดี๋ยวก็หมดเองต้อง purge เพื่อ update ให้ทั่วโลก
เข้ากับ SWR ได้ไหมได้ แต่ผลน้อย (เบราว์เซอร์ยิง background น้อยกว่า)ได้ดี ใช้จริงมากที่สุด
เหมาะกับหน้าประเภทไหนAPI response, HTML dynamicstatic assets, HTML ที่เนื้อหาเหมือนกันทุกคน

สูตรที่ผมใช้ประจำ: ตั้ง max-age สั้นๆ (เช่น 60 วินาที) เพื่อบังคับให้เบราว์เซอร์กลับไปเช็คกับ CDN บ่อยๆ พอมี invalidation ก็ propagate เร็ว ส่วน s-maxage ตั้งยาวๆ (1 ชั่วโมงถึง 1 วัน) เพื่อให้ CDN แคชได้นาน แล้วใช้ stale-while-revalidate เป็น safety net อีกชั้น

ตัวอย่างการตั้งค่า stale-while-revalidate บน Cloudflare, Vercel, Fastly และ nginx

ตอนนี้มาดูของจริงกันครับ ผมจะยก 4 แพลตฟอร์มที่ใช้เยอะที่สุดในปี 2026 พร้อมโค้ดที่ deploy ได้เลย

Cloudflare Workers

Cloudflare รองรับ stale-while-revalidate ตั้งแต่ปี 2021 และในปี 2026 มีตัวเลือกใหม่ผ่าน Cache API ใน Workers ที่ควบคุมได้ละเอียดกว่า

export default {
  async fetch(request, env, ctx) {
    const cache = caches.default;
    const cacheKey = new Request(request.url, request);

    let response = await cache.match(cacheKey);
    if (response) {
      const age = Number(response.headers.get("age") || 0);
      const maxAge = 600;
      if (age > maxAge) {
        // stale, ยิง background revalidation
        ctx.waitUntil(revalidate(request, env, cache, cacheKey));
      }
      return response;
    }

    response = await fetch(request);
    const cloned = new Response(response.body, response);
    cloned.headers.set(
      "Cache-Control",
      "public, max-age=60, s-maxage=600, stale-while-revalidate=86400"
    );
    ctx.waitUntil(cache.put(cacheKey, cloned.clone()));
    return cloned;
  },
};

async function revalidate(request, env, cache, cacheKey) {
  const fresh = await fetch(request);
  await cache.put(cacheKey, fresh.clone());
}

Vercel Edge (Next.js 15 App Router)

บน Vercel ใช้ helper Cache-Control ผ่าน Route Handler หรือ Middleware ได้ตรงๆ Next.js 15 มี revalidateTag() และ revalidatePath() ที่จับคู่กับ tag-based purge บน Vercel Edge Cache อัตโนมัติ

// app/api/products/route.ts
import { NextResponse } from "next/server";

export async function GET() {
  const data = await fetchProducts();

  return NextResponse.json(data, {
    headers: {
      "Cache-Control":
        "public, s-maxage=600, stale-while-revalidate=86400, stale-if-error=604800",
      "CDN-Cache-Control":
        "public, s-maxage=3600, stale-while-revalidate=86400",
    },
  });
}

สังเกตว่า Vercel รองรับ CDN-Cache-Control แยกจาก Cache-Control ปกติ ทำให้ตั้งค่า downstream cache (Vercel Edge) กับ upstream cache (เบราว์เซอร์) แยกกันได้ ตามข้อเสนอใน RFC 9213 Targeted Cache Control

Fastly VCL

Fastly ให้ควบคุม cache lifecycle ผ่าน VCL แบบละเอียดที่สุดในตลาด โดยเฉพาะการใช้ stale-while-revalidate ร่วมกับ request collapsing ที่รวมคำขอที่มาพร้อมกันเป็นคำเดียวไปยัง origin

sub vcl_fetch {
  set beresp.ttl = 10m;
  set beresp.grace = 24h;
  set beresp.stale_while_revalidate = 24h;
  set beresp.stale_if_error = 7d;
}

sub vcl_deliver {
  set resp.http.Cache-Control =
    "public, s-maxage=600, stale-while-revalidate=86400, stale-if-error=604800";
}

nginx เป็น reverse proxy

ถ้าใช้ nginx เป็น edge เอง ต้องเปิด proxy_cache_use_stale updating เพื่อให้ nginx เสิร์ฟ response เก่าในขณะที่มี background revalidation อยู่ (nginx เรียกว่า "cache updating") ตั้งแต่ nginx 1.11.10 เป็นต้นมา

proxy_cache_path /var/cache/nginx keys_zone=main:10m max_size=1g inactive=24h;

server {
  location / {
    proxy_cache main;
    proxy_cache_valid 200 302 10m;
    proxy_cache_background_update on;
    proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504;
    proxy_cache_lock on;

    add_header Cache-Control "public, s-maxage=600, stale-while-revalidate=86400" always;
    proxy_pass http://origin_upstream;
  }
}

ETag, Last-Modified และ Conditional Requests

เมื่อ SWR ทำ background revalidation เนื้อหาที่ CDN ดึงมามัก "ไม่ได้เปลี่ยน" จริง การส่ง full response กลับมาแค่เพื่อรีเซ็ต cache TTL เป็นการเปลืองแบนด์วิดท์มาก ใช้ ETag หรือ Last-Modified ให้ origin ตอบ 304 Not Modified แทน ประหยัดได้มหาศาลในระดับ terabyte ต่อเดือน

// Express example
app.get("/api/products", (req, res) => {
  const data = getProducts();
  const etag = crypto.createHash("sha1").update(JSON.stringify(data)).digest("hex");

  res.setHeader("ETag", `"${etag}"`);
  res.setHeader("Cache-Control", "public, s-maxage=600, stale-while-revalidate=86400");

  if (req.headers["if-none-match"] === `"${etag}"`) {
    return res.status(304).end();
  }
  res.json(data);
});

สังเกตว่า ETag ควรเป็น "strong" (ไม่มี W/ prefix) เมื่อเนื้อหาไบต์ตรงกันเป๊ะ ส่วน weak ETag (W/"abc") ใช้เมื่อเนื้อหาเทียบเท่า semantically แต่อาจต่างในแง่ formatting เล็กน้อย

สำหรับ static asset ที่มี hash ในชื่อไฟล์ (เช่น main.a1b2c3.js) แนะนำใช้ immutable แทน ETag เพราะข้ามการ revalidate ทั้งหมด (เบราว์เซอร์จะไม่ยิง conditional request แม้ user จะรีเฟรชหน้า) ผมเขียนเรื่องนี้ต่อในบทความ เพิ่มประสิทธิภาพรูปภาพเว็บไซต์ปี 2026 ในส่วนของ CDN transform ด้วย

กลยุทธ์ Cache Invalidation และ Tag-Based Purge

"There are only two hard things in Computer Science: cache invalidation and naming things." Phil Karlton พูดไว้และผมเห็นด้วยเต็มร้อย ในปี 2026 เครื่องมือ tag-based purge ทำให้ invalidation เป็นเรื่องน่าปวดหัวน้อยลงมาก

Time-based expiration

วิธีดั้งเดิม แค่รอให้ s-maxage หมด เนื้อหาที่เก่ากว่านั้นจะถูก evict ออกโดยธรรมชาติ ข้อดี: ไม่มี infrastructure เพิ่ม ข้อเสีย: propagation delay สูง ถ้าตั้ง TTL 24 ชม. user บางคนอาจเห็นราคาสินค้าเก่าไปหนึ่งวันเต็ม

Manual purge by URL

ยิง API request ไปยัง CDN เพื่อ purge URL ที่ต้องการ ใช้ได้กับ Cloudflare, Fastly, CloudFront ปกติ แต่ scale ยากเมื่อมี URL ที่เกี่ยวข้องกันเป็นหมื่นหน้า เช่น "อัปเดตราคาสินค้าเดียว → ต้อง purge /products, /category/*, /search/*"

Tag-based purge (แนะนำ)

แนบ Cache-Tag หรือ Surrogate-Key ไปกับ response ตอนแคช เวลาจะ purge ก็ส่ง tag ไปแทน URL:

// origin response
Cache-Control: s-maxage=86400, stale-while-revalidate=604800
Cache-Tag: product-123,category-shoes,homepage

// purge API (Cloudflare)
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone}/purge_cache" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"tags":["product-123"]}'

Vercel มี revalidateTag() ใน Next.js 15 ที่ทำหน้าที่เดียวกัน โดยผูก tag ตอน fetch:

const data = await fetch(url, { next: { tags: ["product-123"] } });

// ในหน้า admin หลังอัปเดตราคา
import { revalidateTag } from "next/cache";
revalidateTag("product-123");

ผมมักคู่ tag-based purge กับ stale-while-revalidate ยาวๆ (7-30 วัน) เพราะเมื่อ purge tag เดียวก็ตัด branch ของ cache ที่เกี่ยวข้องได้หมดในไม่กี่วินาที ไม่ต้องพึ่ง TTL อีกต่อไป

ข้อผิดพลาดที่พบบ่อยเมื่อใช้ Cache-Control

เอาล่ะ มาถึงส่วนที่ผมสนุกที่สุด คือเวลาตรวจให้ลูกค้า ผมเจอ pattern พลาดซ้ำๆ ดังนี้ (ขอสารภาพว่าตัวเองก็เคยพลาดข้อ 3 มาก่อน)

1. ลืม Vary header

ถ้าเสิร์ฟ AVIF ให้ Chrome และ WebP ให้ Safari จาก URL เดียวกันโดยดูจาก Accept header ต้องตั้ง Vary: Accept ไม่งั้น CDN จะแคช AVIF แล้วเสิร์ฟให้ Safari user ที่ไม่รองรับ ทำให้รูปไม่โหลด

Cache-Control: public, s-maxage=31536000, immutable
Vary: Accept, Accept-Encoding

2. แคช HTML ที่มี session cookie

ถ้า HTML มีชื่อ user, tokens, หรือ personalized content อย่าลืมตั้ง Cache-Control: private ไม่งั้นชื่อ user คนหนึ่งจะโผล่ไปให้อีกคนเห็น ผมเจอ incident แบบนี้ที่ทำให้ทีมต้อง roll back cache config กลางดึกมาแล้ว 3 ครั้ง

3. ตั้ง max-age สูงเกินไปสำหรับ HTML

HTML ที่ dynamic ห้ามตั้ง max-age ยาวเกินไป (เช่น 1 ชั่วโมงขึ้นไป) เพราะเบราว์เซอร์จะไม่ยิงคำขอกลับมาเลย พอ purge ที่ CDN ก็ไม่มีผลกับ user คนนั้น ตั้ง max-age=0, s-maxage=3600 แล้วให้ CDN เป็น cache หลัก จะยืดหยุ่นกว่ามาก

4. ใช้ no-cache กับทุก endpoint

มีทีม dev หลายทีมที่ default policy คือ Cache-Control: no-cache ทุก route เพราะกลัวข้อมูลค้าง แต่แบบนี้ทำให้เว็บช้าลงมหาศาลโดยไม่จำเป็น ควรวิเคราะห์ route by route ว่า cacheable หรือไม่ ถ้า cacheable ตั้ง SWR ให้ยาวที่สุดที่ยอมรับได้

5. ลืม stale-if-error

เวลา origin ล่ม (deploy พลาด, DB down) ถ้าไม่มี stale-if-error ผู้ใช้จะเจอหน้า 5xx ทันที ทั้งที่ CDN ยังมี response เก่าอยู่ ตั้ง stale-if-error=604800 (7 วัน) เป็น safety net ให้ user ยังใช้เว็บได้แม้ backend มีปัญหา จากประสบการณ์ผมช่วยลดผลกระทบจาก incident หลายครั้ง

วัดผลกระทบต่อ TTFB และ LCP ด้วย RUM

ก่อนและหลังปรับ cache policy ต้องวัดจริงจาก field ไม่ใช่แค่จาก Lighthouse บน laptop ตัวเอง ผมแนะนำใช้ web-vitals.js library เก็บ TTFB, LCP, INP ของผู้ใช้จริง แล้วแบ่ง segment ตาม cache hit/miss เพื่อดู impact ตรงๆ ผมเขียนวิธีติดตั้งไว้ในคู่มือ ติดตั้ง RUM ด้วย web-vitals.js ปี 2026

ใน Chrome DevTools → Network tab ให้ดู response header cf-cache-status (Cloudflare), x-vercel-cache (Vercel), หรือ x-cache (Fastly, CloudFront) เพื่อยืนยันว่า request นั้น hit หรือ miss ค่าที่ควรเห็นคือ HIT, STALE, REVALIDATED สลับกันไปตามช่วงเวลา ถ้าเห็นแต่ MISS แสดงว่า cache config มีปัญหา ต้องกลับไปเช็ค Vary, cookie, หรือ query param ที่ไม่ควรอยู่ใน cache key

KPI ที่ผมใช้กำกับทีมประจำคือ "cache hit ratio ≥ 90%" และ "TTFB p75 < 200ms ทั่วโลก" ถ้าตัวใดตัวหนึ่งไม่ถึงเป้า จะรีวิว cache policy ทุกไตรมาส

คำถามที่พบบ่อย

stale-while-revalidate ใช้ได้กับเบราว์เซอร์ทุกตัวหรือไม่?

ปัจจุบันเบราว์เซอร์หลักทั้ง Chrome, Edge, Firefox, Safari รองรับ stale-while-revalidate ใน Cache-Control แล้ว แต่ประโยชน์หลักคือฝั่ง shared cache (CDN) ที่รองรับเต็มรูปแบบและใช้บ่อยกว่า สำหรับ HTTP client อื่นๆ อย่าง curl หรือ library บาง server ที่ไม่ implement RFC 5861 คำสั่งนี้จะถูกละเลยและ fallback ไปที่ max-age ปกติ

ควรตั้งค่า stale-while-revalidate นานเท่าไหร่?

ขึ้นกับความยอมรับได้ของ "ความเก่า" ของเนื้อหา สำหรับ blog หรือ marketing pages ตั้ง 7-30 วันได้สบาย สำหรับ product listing ที่ราคาเปลี่ยนบ่อย ตั้ง 1-24 ชั่วโมงและใช้ tag-based purge ประกอบ ผมเลี่ยงการตั้งเกิน 30 วัน เพราะ CDN บางเจ้ามี hard cap และเพราะเวลานานเกินไปทำให้ debug ยาก

stale-while-revalidate ต่างจาก stale-if-error อย่างไร?

stale-while-revalidate เสิร์ฟ stale content ในกรณีปกติที่แคชหมดอายุ ระหว่างยิง background revalidation ที่สำเร็จ ส่วน stale-if-error เสิร์ฟ stale content เฉพาะเมื่อ origin คืน error 5xx หรือ timeout เท่านั้น เป็น safety net คนละสถานการณ์ ใช้พร้อมกันได้และแนะนำให้ใช้ควบคู่

Cache-Control กับ Expires header ต่างกันอย่างไร?

Expires เป็น header เก่าจาก HTTP/1.0 ระบุเวลาหมดอายุเป็น absolute date ซึ่งพึ่ง clock ของ client และ server ที่ตรงกัน ส่วน Cache-Control: max-age เป็น relative duration ที่แม่นยำและใหม่กว่า ถ้ามีทั้งคู่ Cache-Control จะชนะเสมอ ในปี 2026 แนะนำใช้ Cache-Control อย่างเดียวและลบ Expires ออก

SWR ทำงานร่วมกับ Service Worker ได้ไหม?

ได้ครับ Service Worker เขียน strategy "stale-while-revalidate" เองได้ในระดับ JavaScript ผ่าน Cache API library อย่าง Workbox มี strategy สำเร็จรูปชื่อ StaleWhileRevalidate ให้ใช้ได้เลย แนวคิดคล้ายกับ Cache-Control แต่ทำงานฝั่ง client ล้วน เหมาะกับ PWA offline-first

ใช้ SWR แล้วยังต้องใช้ ISR ของ Next.js ไหม?

ISR (Incremental Static Regeneration) ของ Next.js คือการสร้าง SWR ในระดับ framework โดย regenerate page แบบ background หลัง revalidation period ผ่านไป ถ้าใช้ Next.js บน Vercel ISR จะ integrated กับ Edge Cache อยู่แล้ว ไม่ต้องเซ็ต Cache-Control เอง แต่ถ้าใช้ self-host หรือ framework อื่น การตั้ง SWR ผ่าน Cache-Control ให้ผลใกล้เคียงกัน

Mateo Silva
เกี่ยวกับผู้เขียน Mateo Silva

Full-stack performance lead bridging frontend perf with backend latency. Cache invalidation is his love language.