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 ไปเลยนะ

ลองเอาไปใช้ดูนะครับ บอกเลยว่าชีวิตนักพัฒนาจะง่ายขึ้นเยอะ!

Read more

ไอลีน กู: ตำนานนักสกีฟรีสไตล์ผู้พลิกโฉมวงการและความหมายของชัยชนะ

ไอลีน กู: ตำนานนักสกีฟรีสไตล์ผู้พลิกโฉมวงการและความหมายของชัยชนะ

เจาะลึกเรื่องราวของ Eileen Gu นักสกีฟรีสไตล์ผู้สร้างประวัติศาสตร์ในโอลิมปิก 2026 สถิติที่ไม่เคยมีมาก่อน ประเด็นถกเถียง และความแข็งแกร่งส่วนตัวที่ทำให้เธอก้าวสู่ระดับโลก

By ทีมงาน devdog
วันพระ: คู่มือฉบับสมบูรณ์สำหรับพุทธศาสนิกชนและผู้สนใจยุคใหม่

วันพระ: คู่มือฉบับสมบูรณ์สำหรับพุทธศาสนิกชนและผู้สนใจยุคใหม่

เจาะลึกวันพระและความสำคัญของวันมาฆบูชา 2569 ทั้งวันหยุดราชการ ธนาคาร กิจกรรมเวียนเทียนต้นไม้ และผลกระทบต่อบริการขนส่ง เตรียมตัววางแผนทำบุญและพักผ่อน

By ทีมงาน devdog
ถอดรหัสรักแท้: "บังมัดคลองตันต้นข้าว" เรื่องราวที่สะท้อนการให้อภัยและการเริ่มต้นใหม่

ถอดรหัสรักแท้: "บังมัดคลองตันต้นข้าว" เรื่องราวที่สะท้อนการให้อภัยและการเริ่มต้นใหม่

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

By ทีมงาน devdog
ไฮไลท์บอลไทยลีก 2: มหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรงสู่เส้นทางเพลย์ออฟ

ไฮไลท์บอลไทยลีก 2: มหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรงสู่เส้นทางเพลย์ออฟ

เจาะลึกไฮไลท์บอลไทยลีก 2 ของมหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรง ชัยชนะสำคัญจาก ชิตชนก และบทบาทโค้ชดุสิต สู่เส้นทางเพลย์ออฟที่น่าจับตา!

By ทีมงาน devdog