สิ่งที่ 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 ได้
ลำดับชั้นที่เหมาะสำหรับทีมส่วนใหญ่
- Browser cache – ไฟล์ Static (CSS, JS, รูปภาพ) จะได้รับค่า
max-ageที่ยาวนาน เพื่อให้อุปกรณ์ของผู้ใช้ไม่ต้องร้องขอข้อมูลจากเครือข่ายซ้ำอีก - CDN – Edge nodes จะแคชหน้า HTML และ API JSON แบบเต็มรูปแบบ โดยปฏิบัติตาม header
Cache-Controlที่คุณตั้งค่าไว้ใน Next.js - Reverse proxy – instance ของ Nginx หรือ Varnish จะวางอยู่หน้าเซิร์ฟเวอร์ Next.js เพื่อส่งมอบการตอบสนองที่แคชไว้สำหรับเส้นทาง (routes) ที่ไม่ค่อยมีการเปลี่ยนแปลง
- Next.js internal cache – แคชข้อมูลและแคชเส้นทางของเฟรมเวิร์กจะจัดการเรื่อง per-request memoization และ ISR
- Application cache – Redis จะเก็บผลลัพธ์จากการ query ฐานข้อมูลที่หนักหน่วง โดยใช้ query parameters หรือแท็กเป็น key
- Database – แหล่งข้อมูลที่ถูกต้องที่สุด (source of truth) ซึ่งจะถูก query ก็ต่อเมื่อเกิด cache miss ในทุกเลเยอร์ที่อยู่สูงกว่า
เมื่อมีการร้องขอเข้ามา มันจะไล่ตรวจสอบตามรายการนี้จนกว่าจะมีเลเยอร์ใดเลเยอร์หนึ่งตอบกลับ คำตอบที่เร็วที่สุดจะเป็นผู้ชนะ และการตอบสนองนั้นจะถูกเขียนกลับขึ้นไปตามลำดับชั้นเพื่อใช้สำหรับการเรียกใช้งานในอนาคต
การนำทุกส่วนมาประกอบกัน
- ตั้งค่า header
Cache-Controlใน API routes ของคุณ - กำหนดค่า CDN ของคุณ – เปิดใช้งาน edge caching สำหรับ HTML และ JSON endpoints และตรวจสอบให้แน่ใจว่า CDN ปฏิบัติตาม
Cache-Control - ติดตั้ง reverse proxy – วาง Nginx ไว้หน้าเซิร์ฟเวอร์ Next.js ของคุณ
- เพิ่ม Redis – แคชผลลัพธ์จากการ query ฐานข้อมูลที่ใช้ทรัพยากรสูง
- ใช้
revalidateTagใน component ของคุณ – เรียกใช้revalidateTagเพื่อล้างแคชภายในของ Next.js สำหรับแท็กที่ระบุ
บทสรุป
แคชเพียงอย่างเดียวภายใน Next.js ช่วยให้การร้องขอครั้งแรกเร็วขึ้น แต่ความสามารถในการขยายตัว (scalability) ที่แท้จริงมาจาก stack ที่มีการจัดการอย่างเป็นระบบ: browser → CDN → reverse proxy → framework → Redis → database. จงกำหนดค่าแต่ละเลเยอร์ จัดการการทำ invalidation ด้วยเวลา, เหตุการณ์ (events) และแท็ก พร้อมทั้งเฝ้าดูตัวชี้วัด (metrics) ของคุณ แล้วคุณจะพบว่า latency ยังคงต่ำในขณะที่ backend ของคุณสามารถรองรับการพุ่งสูงขึ้นของ traffic ได้
