Astro 7.2 Incremental Builds: เร่งเว็บใหญ่ด้วย cacheKey โดยไม่เสี่ยงหน้าเก่า
เว็บ static โตขึ้นแบบเงียบ ๆ ครับ ช่วงแรก build สิบหน้าแทบไม่รู้สึก พอมีบทความหลายร้อยหน้า ทุก commit ที่แก้คำหนึ่งคำกลับต้อง render ทั้งคลัง เหมือนร้านหนังสือพิมพ์หนังสือทุกเล่มใหม่เพราะแก้ป้ายราคาหน้าร้าน
Astro 7.2 เพิ่ม incremental static builds แบบทดลอง เพื่อให้ build รอบถัดไปนำ output ของ route ที่ไม่เปลี่ยนกลับมาใช้ได้ ฟีเจอร์นี้น่าสนใจมากกับเว็บเนื้อหา แต่คำว่า cache มาพร้อมคำถามสำคัญกว่าเรื่องความเร็ว: เราพิสูจน์ได้อย่างไรว่าหน้าที่ “ดูเหมือนไม่เปลี่ยน” ไม่ได้พึ่งข้อมูลที่เปลี่ยนอยู่ข้างหลัง
ประกาศ Astro 7.2 อธิบายว่าระบบดูทั้ง hash ของ module graph และ cacheKey ที่เราคืนจาก getStaticPaths() ถ้าทั้งสองส่วนเท่าเดิม Astro จึงนำหน้าที่ build ไว้กลับมาใช้
WHY: ความเร็วที่แลกกับความสดของข้อมูลไม่ใช่ของฟรี
สมมติหน้า /blog/a อ่านไฟล์บทความ A แต่ layout ยังดึงชื่อเว็บไซต์จาก config กลาง ถ้าแก้ layout ตัว module hash เปลี่ยน หน้า A ต้อง render ใหม่ นี่เป็นส่วนที่ Astro ติดตามให้
แต่ถ้าหน้าดึงข้อมูลภายนอกหรือ loader ส่ง object ที่เนื้อหาเปลี่ยนโดย path เดิม Astro ไม่รู้ความหมายของข้อมูลนั้น เราจึงต้องส่ง cacheKey ที่เปลี่ยนตามเนื้อหา เหมือนติดวันที่หมดอายุบนกล่อง: ชื่อกล่องเท่าเดิมไม่พอ ต้องรู้ว่าสินค้าข้างในเป็นล็อตเดิมหรือไม่
ข้อผิดพลาดที่อันตรายมีสองด้าน:
- key เปลี่ยนบ่อยเกินไป: build ยังถูกต้อง แต่แทบไม่ประหยัดเวลา
- key ไม่เปลี่ยนเมื่อเนื้อหาเปลี่ยน: build เร็วขึ้น แต่ผู้ใช้อ่านหน้าเก่า
ด้านหลังร้ายแรงกว่า จึงควรวัด correctness ก่อน duration
HOW: รูปแบบ config และ route
ฟีเจอร์ยังเป็น experimental ต้องเปิดอย่างตั้งใจ ตัวอย่างต่อไปนี้เป็น fragment สำหรับอธิบายตำแหน่ง config ไม่ใช่ไฟล์ครบทั้งโปรเจกต์ จึงตรวจเทียบกับเอกสารและ type check ของ repository แทนการอ้างว่า snippet เดี่ยวรันได้
// astro.config.mjs — ตัวอย่างบางส่วน
export default defineConfig({
experimental: {
incrementalBuild: true,
},
});
ใน dynamic route เราคืน cacheKey คู่กับ params และ props ตัวอย่างด้านล่างเป็น fragment เช่นกัน โดย post.digest หมายถึง fingerprint ของเนื้อหา ไม่ใช่วันที่ build ปัจจุบัน
---
// src/pages/blog/[...slug].astro — ตัวอย่างบางส่วน
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.id },
props: { post },
cacheKey: post.digest,
}));
}
---
ถ้าไม่คืน cacheKey route นั้นจะ render ทุกครั้ง นี่เป็นค่าเริ่มต้นที่ปลอดภัยและเหมาะกับหน้าที่ dependency ยังนับไม่ครบ Cache ถูกเก็บใต้ cacheDir ซึ่งค่าเริ่มต้นอยู่ที่ node_modules/.astro ดังนั้น CI ที่เริ่มจาก filesystem ว่างทุกครั้งจะยังไม่เร็วขึ้นจนกว่าจะ restore directory นี้อย่างมีขอบเขต
แผน rollout สำหรับ repository นี้
repository ปัจจุบันใช้ Astro 7.2.9 ซึ่งเป็น stable ล่าสุดจาก registry ณ วันที่ตรวจ แต่ route บทความยังไม่ได้ส่ง cacheKey และ config ยังไม่ได้เปิด flag ทดลอง พี่โชว์จึงไม่เปิดฟีเจอร์ใน content PR นี้ เพราะการเปลี่ยน build pipeline ควรมี PR แยกพร้อม baseline
แผนที่ตรวจสอบได้มีสี่ช่วง:
- Baseline: เก็บเวลา build, จำนวนหน้า และขนาด
distจาก clean cache อย่างน้อยสามรอบ - Correctness fixture: สร้างบทความทดสอบสองหน้า แก้เฉพาะหน้าเดียว แล้วตรวจทั้ง HTML, OG path, index และ internal links
- Warm cache: เก็บ
node_modules/.astroเป็น CI cache โดยผูกกับ lockfile, Astro version และ config ที่เกี่ยวข้อง - Failure drill: จงใจแก้ layout กลางและ design token เพื่อยืนยันว่าทุกหน้าที่พึ่ง module นั้นถูก render ใหม่
อย่าใช้เวลา build ลดลงเป็นหลักฐานเดียว ให้เปรียบเทียบ manifest ของไฟล์และเนื้อหา HTML ด้วย ถ้าหน้าหนึ่งควรเปลี่ยนแต่ timestamp หรือ hash ของ output ไม่เปลี่ยน เราพบ invalidation bug ก่อนผู้ใช้พบ
เมื่อไรยังไม่ควรเปิด
ถ้า route อ่านข้อมูลจาก environment, เวลา current time, endpoint ภายนอก หรือ filesystem ที่ไม่ได้สะท้อนอยู่ใน cacheKey ให้หยุดก่อน การใส่ Date.now() ลง key ทำให้ปลอด stale แต่เท่ากับปิดประโยชน์ incremental ทั้งหมด คำตอบที่ดีกว่าคือสร้าง digest จาก input จริงหรือยอมให้ route นั้น render ทุกครั้ง
ฟีเจอร์ experimental อาจเปลี่ยน API ในรุ่นถัดไป ทีมจึงควรล็อกเวอร์ชัน, อ่าน release notes และเก็บ rollback เป็นการปิด flag ไม่ควรผูก production pipeline จนถอยกลับยาก
Recap
Incremental builds ของ Astro 7.2 ใช้ module hash ร่วมกับ cacheKey เพื่อ reuse หน้าเดิมอย่างมีเงื่อนไข ฟีเจอร์นี้เหมาะกับเว็บเนื้อหาที่ input มี digest ชัดเจน แต่ไม่ควรเปิดเพียงเพราะ build เริ่มช้า
วัด baseline, ออกแบบ key จากข้อมูลจริง, ทดสอบทั้งกรณีควร reuse และควร invalidate แล้วค่อยเปิดใน PR แยก ความเร็วที่ดีคือความเร็วที่ยังส่งหน้าใหม่ถูกหน้า ไม่ใช่แค่ตัวเลข CI สีเขียวครับ