Next.js ISR: เว็บอัปเดตเองได้ ไม่ต้องกด Deploy บ่อยๆ
คือว่า ปกติเวลาเราทำเว็บ static ด้วย Next.js อ่ะ มันก็ดีตรงที่โหลดเร็ว เซิร์ฟเวอร์ไม่เปลืองแรง แต่มันก็มีปัญหาอยู่นิดหน่อยนะ คือถ้าข้อมูลในเว็บเราเปลี่ยนบ่อยๆ เช่น พวกบทความสินค้า หรือราคา เราก็ต้องมานั่ง next build แล้วก็ next export ใหม่ แล้ว deploy ขึ้นไปทุกครั้ง โคตรเสียเวลาเลยใช่มั้ยล่ะ?
ทีนี้ Next.js เขาก็มีของเล่นใหม่ที่เรียกว่า Incremental Static Regeneration (ISR) มันคือการผสมผสานข้อดีของ Static Site Generation (SSG) กับ Server-Side Rendering (SSR) เข้าด้วยกัน คือหน้าเว็บมันยังคงเป็น static นะ แต่พอถึงเวลาที่เรากำหนด มันจะแอบไป fetch ข้อมูลใหม่มาสร้างหน้าเพจให้ใหม่เองเบื้องหลัง โดยที่ผู้ใช้ก็ยังเห็นหน้าเดิมที่โหลดไว้ก่อน จนกว่าหน้าใหม่จะพร้อมค่อยสลับไปแสดง เจ๋งป้ะล่ะ!
หลักการมันง่ายๆ เลยครับ เราแค่เพิ่ม revalidate เข้าไปใน getStaticProps แค่นั้นเอง มาดูโค้ดตัวอย่างกันเลยดีกว่า
สมมติว่าเรามีหน้าบทความ pages/posts/[id].js:
// pages/posts/[id].js
import React from 'react';
function Post({ post }) {
if (!post) {
return <div>Loading...</div>; // กรณี fallback: true
}
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
<small>Last updated: {new Date().toLocaleString()}</small>
</div>
);
}
export async function getStaticPaths() {
// สมมติว่าดึง ID มาจาก API หรือฐานข้อมูล
const posts = [{ id: '1', title: 'โพสต์แรก', content: 'เนื้อหาโพสต์แรก' }];
const paths = posts.map(post => ({
params: { id: post.id },
}));
return {
paths,
fallback: 'blocking', // สำคัญมากนะจุดนี้! เดี๋ยวจะเล่าว่าทำไมต้องมี
};
}
export async function getStaticProps({ params }) {
const { id } = params;
console.log(`Fetching post ${id} at ${new Date().toLocaleString()}`);
// ลองจำลองการ fetch จาก API ที่ข้อมูลอาจจะเปลี่ยนได้
// ในโลกจริงตรงนี้จะเป็น API call จริงๆ
const data = await new Promise(resolve => {
setTimeout(() => {
const mockData = {
'1': {
id: '1',
title: `โพสต์ที่ ${id} (อัปเดตล่าสุด)`,
content: `นี่คือเนื้อหาสำหรับโพสต์ ${id} อัปเดตเมื่อ ${new Date().toLocaleString()}`,
},
};
resolve(mockData[id]);
}, 1000); // จำลองการดีเลย์ 1 วินาที
});
if (!data) {
return { notFound: true };
}
return {
props: {
post: data,
},
revalidate: 10, // จะสร้างหน้าใหม่ทุกๆ 10 วินาที ถ้ามีคนเข้าหน้าเว็บนี้
};
}
export default Post;
อธิบายโค้ดนิดนึง:
revalidate: 10: ตรงนี้แหละคือหัวใจของ ISR! Next.js จะบอกว่า “เฮ้ย หน้าเพจนี้มัน Expired ทุก 10 วินาทีนะ” พอมีคนเข้าหน้าเพจนี้หลัง 10 วินาทีไปแล้ว Next.js จะ serve หน้าเดิมที่ cache ไว้ให้คนนั้นเห็นไปก่อน แล้วเบื้องหลังก็จะไปเรียกgetStaticPropsใหม่เพื่อสร้างหน้าเวอร์ชันอัปเดต พอเสร็จแล้วก็จะเอามาใช้กับคนต่อไปที่เข้ามาดูหน้าเดิมfallback: 'blocking': สำคัญมากๆ เลยตรงนี้ ผมเคยพลาดมาแล้ว โคตรปวดหัวเลยครับ!
เรื่อง fallback ที่ผมเคยพลาด (และคุณก็อาจจะพลาดเหมือนกัน)
ตอนแรกๆ ผมก็แบบ ไม่ได้ใส่ fallback หรือใส่เป็น false แล้วพอมี id ใหม่ๆ ที่เราไม่ได้ pre-render ไว้ใน getStaticPaths ปรากฏว่า... 404 Not Found ครับพี่น้อง! ทั้งๆ ที่เราอยากให้มันไป fetch มาสร้างใหม่ให้ด้วยซ้ำไป
Error ที่เจอ (สมมตินะ): ถ้าเราไม่ได้กำหนด fallback: 'blocking' หรือ fallback: true ไว้ใน getStaticPaths แล้วมี id ใหม่ๆ ที่ไม่ได้อยู่ใน paths ที่เราคืนค่าไป เช่น คุณมีแค่ id: '1' ใน paths แต่ดันไปเข้า localhost:3000/posts/2 สิ่งที่คุณจะเห็นคือ หน้า 404 โดยไม่มีการพยายามไปเรียก getStaticProps สำหรับ id: '2' เลย
วิธีแก้:
ให้กำหนด fallback เป็น true หรือ blocking:
fallback: true: หน้าเว็บจะแสดง 'Loading...' ชั่วคราว (หรือ UI ที่เราออกแบบไว้) ในขณะที่ Next.js กำลังfetchข้อมูลเพื่อสร้างหน้าใหม่ พอเสร็จก็แสดงผลfallback: 'blocking': อันนี้จะบล็อกการแสดงผลไว้เลยจนกว่าgetStaticPropsจะทำงานเสร็จ และหน้าใหม่จะถูกสร้างขึ้นมา แล้วค่อยแสดงผล เหมือน SSR แต่ว่ามันจะcacheหน้าที่สร้างเสร็จไว้ให้ด้วยสำหรับคนที่มาทีหลัง ทำให้โหลดเร็วขึ้น
ส่วนตัวผมชอบ blocking มากกว่านะ มันดูเนียนกว่าสำหรับผู้ใช้ ไม่ต้องเห็น 'Loading...' บ่อยๆ แต่ถ้าเป็นเคสที่ getStaticProps มันนานมากๆ true ก็เป็นทางเลือกที่ดี
ข้อคิดเห็นส่วนตัว:
ISR มันคือคำตอบสำหรับหลายๆ โปรเจกต์ที่ต้องการความเร็วแบบ Static site แต่ก็อยากอัปเดตข้อมูลได้ง่ายๆ โดยไม่ต้องวุ่นวายกับการ deploy บ่อยๆ มันช่วยลดภาระ CI/CD และยังลดโหลดเซิร์ฟเวอร์ตอน build ไปได้เยอะเลยนะ เหมาะกับพวกบล็อก, เว็บข่าว, หรือ E-commerce ที่ข้อมูลสินค้าไม่ได้เปลี่ยนทุกวินาที แต่ก็ต้องเข้าใจ revalidate ดีๆ ล่ะว่าอยากให้มันอัปเดตบ่อยแค่ไหน ถ้าข้อมูลเปลี่ยนแบบเรียลไทม์จ๋าๆ อาจจะต้องมองไปทาง SSR หรือ Client-side fetching ไปเลยนะ
ลองเอาไปใช้ดูนะครับ บอกเลยว่าชีวิตนักพัฒนาจะง่ายขึ้นเยอะ!