สิ่งที่ Next.js มีให้คุณตั้งแต่เริ่มต้น

  • Data cache – ใน Next.js 13+ ทุกการเรียก fetch() จะถูกเก็บไว้ในหน่วยความจำแคชแบบ per-request การเพิ่มตัวเลือก revalidate จะเป็นการบอก runtime ให้รีเฟรชข้อมูลหลังจากผ่านช่วงเวลาที่กำหนด ช่วยเปลี่ยนจากการเรียก API แบบ hot ให้กลายเป็น cold เฉพาะเมื่อจำเป็นเท่านั้น
  • Full-route cache – เฟรมเวิร์กจะเก็บ HTML ที่เรนเดอร์แล้วและข้อมูลที่จำเป็นสำหรับทั้งเส้นทาง (route) เอาไว้ ผู้ที่กลับมาเยี่ยมชมอีกครั้งจะเห็นการเปลี่ยนหน้าอย่างรวดเร็วทันที เพราะเซิร์ฟเวอร์ข้ามขั้นตอนการ re-rendering ไป
  • ISR – หน้าเว็บแบบ Static จะถูกสร้างขึ้นใหม่ในพื้นหลัง ในขณะที่เวอร์ชันเก่าก็ยังคงให้บริการผู้ใช้งานต่อไป สิ่งนี้ช่วยให้คุณรักษาเว็บไซต์ขนาดใหญ่ให้เป็นแบบ static ได้โดยไม่ต้องทำการ rebuild ใหม่ทั้งหมดทุกครั้งที่มีการเปลี่ยนแปลงเนื้อหา
  • Server Component cache() – ตัวช่วย cache ของ React จะทำ memoization สำหรับการคำนวณที่ใช้ทรัพยากรสูงหรือการเชื่อมต่อฐานข้อมูลตลอดระยะเวลาของการร้องขอ (request) เพียงครั้งเดียว เพื่อหลีกเลี่ยงการทำงานซ้ำซ้อนภายใน component tree ของหน้าเว็บ

แคชเหล่านี้ช่วยแก้ปัญหา "first-hit" ได้ แต่ยังคงทำงานอยู่ภายในกระบวนการของ Node เพื่อปกป้องเซิร์ฟเวอร์และฐานข้อมูล คุณควรขยายการทำแคชออกไปสู่เลเยอร์ภายนอก

เลเยอร์ภายนอกที่คุณสามารถเพิ่มได้

เลเยอร์ สิ่งที่จัดเก็บ เครื่องมือทั่วไป ช่วยได้อย่างไร
CDN Static assets, HTML, API JSON Cloudflare, Akamai, AWS CloudFront ย้ายเนื้อหาไปยัง edge locations ช่วยลดระยะเวลาการรับส่งข้อมูล (round-trip) ไปยังผู้ใช้
Reverse proxy การตอบสนองของทั้งหน้าเว็บก่อนถึง Node Nginx, Varnish ส่งมอบหน้าเว็บที่แคชไว้โดยตรง ช่วยลดภาระของ Next.js instance
Application-level cache ผลลัพธ์จากการ query ฐานข้อมูลหรือการเรียก API ที่ใช้ทรัพยากรสูง Redis, Memcached ให้บริการ key-value store ที่รวดเร็ว ซึ่งข้อมูลจะไม่หายไปเมื่อเซิร์ฟเวอร์รีสตาร์ท และสามารถแชร์ข้อมูลร่วมกันระหว่างแอปพลิเคชันหลาย instance ได้

แต่ละเลเยอร์จะอยู่ห่างจากฐานข้อมูลต้นทางมากขึ้น ดังนั้นหากเกิด cache miss ในเลเยอร์หนึ่ง มันจะส่งผลต่อไปยังเลเยอร์ถัดไป และจะเข้าถึงฐานข้อมูลก็ต่อเมื่อจำเป็นจริงๆ เท่านั้น

การรักษาความสดใหม่ของแคช

การทำ Invalidation (การล้างแคช) เป็นสิ่งที่ทำให้หลายทีมติดขัด นี่คือ 3 รูปแบบที่ใช้งานได้จริง:

  • Time-based (TTL) – กำหนดเวลาหมดอายุที่แน่นอนให้กับข้อมูลในแคช ตั้งค่าได้ง่าย แต่อาจแสดงข้อมูลที่ล้าสมัยจนกว่าเวลาจะหมดลง
  • Event-driven – เชื่อมต่อกับ CMS หรือแหล่งข้อมูลใดๆ ที่ส่ง webhook เมื่อเนื้อหาเปลี่ยน โดย webhook จะทำหน้าที่สั่งล้าง (purge) ข้อมูลแคชที่เกี่ยวข้อง
  • Tag-based – แนบแท็กเชิงตรรกะให้กับกลุ่มของการ fetch (เช่น product-list) เมื่อมีรายการใดก็ตามในกลุ่มนั้นเปลี่ยน การเรียก revalidateTag('product-list') เพียงครั้งเดียวจะล้างข้อมูลทุกรายการที่มีแท็กเดียวกัน

การผสมผสานแนวทางเหล่านี้จะช่วยให้คุณสร้างสมดุลระหว่างความสดใหม่ของข้อมูลและอัตราการเกิด cache-hit ได้

ลำดับชั้นที่เหมาะสำหรับทีมส่วนใหญ่

  1. Browser cache – ไฟล์ Static (CSS, JS, รูปภาพ) จะได้รับค่า max-age ที่ยาวนาน เพื่อให้อุปกรณ์ของผู้ใช้ไม่ต้องร้องขอข้อมูลจากเครือข่ายซ้ำอีก
  2. CDN – Edge nodes จะแคชหน้า HTML และ API JSON แบบเต็มรูปแบบ โดยปฏิบัติตาม header Cache-Control ที่คุณตั้งค่าไว้ใน Next.js
  3. Reverse proxy – instance ของ Nginx หรือ Varnish จะวางอยู่หน้าเซิร์ฟเวอร์ Next.js เพื่อส่งมอบการตอบสนองที่แคชไว้สำหรับเส้นทาง (routes) ที่ไม่ค่อยมีการเปลี่ยนแปลง
  4. Next.js internal cache – แคชข้อมูลและแคชเส้นทางของเฟรมเวิร์กจะจัดการเรื่อง per-request memoization และ ISR
  5. Application cache – Redis จะเก็บผลลัพธ์จากการ query ฐานข้อมูลที่หนักหน่วง โดยใช้ query parameters หรือแท็กเป็น key
  6. Database – แหล่งข้อมูลที่ถูกต้องที่สุด (source of truth) ซึ่งจะถูก query ก็ต่อเมื่อเกิด cache miss ในทุกเลเยอร์ที่อยู่สูงกว่า

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

การนำทุกส่วนมาประกอบกัน

  1. ตั้งค่า header Cache-Control ใน API routes ของคุณ
  2. กำหนดค่า CDN ของคุณ – เปิดใช้งาน edge caching สำหรับ HTML และ JSON endpoints และตรวจสอบให้แน่ใจว่า CDN ปฏิบัติตาม Cache-Control
  3. ติดตั้ง reverse proxy – วาง Nginx ไว้หน้าเซิร์ฟเวอร์ Next.js ของคุณ
  4. เพิ่ม Redis – แคชผลลัพธ์จากการ query ฐานข้อมูลที่ใช้ทรัพยากรสูง
  5. ใช้ revalidateTag ใน component ของคุณ – เรียกใช้ revalidateTag เพื่อล้างแคชภายในของ Next.js สำหรับแท็กที่ระบุ

บทสรุป

แคชเพียงอย่างเดียวภายใน Next.js ช่วยให้การร้องขอครั้งแรกเร็วขึ้น แต่ความสามารถในการขยายตัว (scalability) ที่แท้จริงมาจาก stack ที่มีการจัดการอย่างเป็นระบบ: browser → CDN → reverse proxy → framework → Redis → database. จงกำหนดค่าแต่ละเลเยอร์ จัดการการทำ invalidation ด้วยเวลา, เหตุการณ์ (events) และแท็ก พร้อมทั้งเฝ้าดูตัวชี้วัด (metrics) ของคุณ แล้วคุณจะพบว่า latency ยังคงต่ำในขณะที่ backend ของคุณสามารถรองรับการพุ่งสูงขึ้นของ traffic ได้