Buggy
← บทความทั้งหมด

Next.js Caching: เข้าใจว่าอะไรถูก cache ตอนไหน

· 2 min read

#nextjs#web

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

cache มีหลายชั้น ไม่ใช่ชั้นเดียว

  • Request memoization — ภายใน render รอบเดียวกัน fetch ที่ URL และ options เหมือนกันจะถูกยิงจริงครั้งเดียว หมดอายุเมื่อ render จบ
  • Data cache — ผลลัพธ์ของ data fetch ที่ถูกเก็บข้าม request และข้าม deploy จนกว่าจะถูก revalidate
  • Full route cache — HTML และ RSC payload ของ route ที่ถูก prerender ตอน build
  • Router cache — cache ฝั่ง client ที่เก็บ payload ของ route ที่เพิ่งเยี่ยมชม เพื่อให้การกดย้อนกลับไม่ต้องโหลดใหม่

เวลา debug ให้ไล่จากชั้นนอกสุดเข้ามา เพราะการเห็นข้อมูลเก่าบนหน้าจอ ส่วนใหญ่มาจาก router cache หรือ full route cache ไม่ใช่จาก data cache

บอกความตั้งใจให้ชัดที่ระดับ fetch

// ข้อมูลที่เปลี่ยนไม่บ่อย: ยอมให้เก่าได้ไม่เกิน 1 ชั่วโมง
const posts = await fetch(url, { next: { revalidate: 3600 } })
 
// ข้อมูลที่ต้องสดเสมอ
const cart = await fetch(url, { cache: 'no-store' })

การใส่ revalidate ที่ระดับ fetch อ่านง่ายกว่าการตั้งที่ระดับ segment เพราะคนอ่านโค้ดเห็นทันทีว่าข้อมูลก้อนนี้ยอมเก่าได้แค่ไหน

ล้าง cache แบบเจาะจงด้วย tag

const posts = await fetch(url, { next: { tags: ['posts'] } })
 
// เรียกหลังจากบันทึกบทความใหม่
revalidateTag('posts')

วิธีนี้ดีกว่าการตั้งเวลา revalidate สั้นๆ เพราะข้อมูลจะสดทันทีที่มีการแก้ไขจริง โดยไม่ต้องยิงซ้ำทุกๆ ไม่กี่วินาทีแบบเปล่าประโยชน์

ตรวจว่าหน้าไหนเป็น static จริง

next build

ผลลัพธ์ตอน build จะบอกว่าแต่ละ route เป็น static หรือ dynamic ถ้าหน้าที่ตั้งใจให้เป็น static กลายเป็น dynamic ให้ไล่ดูว่ามีการเรียก API ที่ผูกกับ request หรือเปล่า เช่น cookies(), headers() หรือการอ่าน searchParams ซึ่งทำให้ Next.js ต้องเรนเดอร์ตอนมี request เข้ามาเท่านั้น

เลิกใช้ cache ชั่วคราวเวลาไล่ปัญหา

ตอน debug ให้ปิด cache ทีละชั้นแล้วดูว่าอาการหายที่ชั้นไหน วิธีนี้เร็วกว่าการเดาว่าเป็นที่ CDN หรือที่ตัวแอป และช่วยไม่ให้ลงเอยด้วยการใส่ no-store ทุกที่จนเว็บช้าโดยไม่จำเป็น