14 คะแนน โดย xguru 2025-02-24 | 3 ความคิดเห็น | แชร์ทาง WhatsApp
  • ทิศทางอนาคตของ Next.js น่าสนใจมาก
  • แม้จะมีประเด็นเกี่ยวกับ Server Actions แต่ก็ดูมีแนวโน้มว่าจะดีขึ้นด้วย useOptimistic, useFormStatus ของ React 19
    • แนวทาง useFetcher ของ Remix ก็ให้ DX ที่ดีเช่นกัน
  • PPR (Partial Pre-rendering) และระบบแคชแบบ granular ใหม่ของ Next.js โดดเด่นเป็นพิเศษ
  • โดยรวมแล้วให้ความรู้สึกในแง่บวกมาก

The Big Picture

  • สามารถเปิดใช้ระบบแคชใหม่แบบ experimental ได้ใน next.config.js
  • สามารถกำหนด cache profile เพื่อตั้งค่าเวลาหมดอายุและรอบการ revalidate ได้หลากหลายแบบ
// next.config.js  
const config = {  
  experimental: {  
    // เปิดใช้ระบบ caching ใหม่ ตอนนี้สามารถใช้ `use cache` ในโค้ดได้   
    dynamicIO: true,   
    // ตัวเลือกเสริม: ตั้งค่า cache profile   
    cacheLife: {  
      blog: {  
        stale: 3600, // เก็บ client cache: 1 ชั่วโมง  
        revalidate: 900, // รีเฟรชจากเซิร์ฟเวอร์: 15 นาที  
        expire: 86400, // อายุสูงสุด: 1 วัน  
      },  
    },  
  },  
};  

การใช้งานพื้นฐานของ use cache

  • สามารถแคชได้ในระดับไฟล์ คอมโพเนนต์ และฟังก์ชัน ผ่านการประกาศ "use cache"
  • จากตัวอย่างโค้ด สามารถเพิ่ม use cache เพื่อใช้แคชได้อย่างง่ายดาย
  • สามารถใช้ cacheTag, revalidateTag เป็นต้น เพื่อทำ invalidation ของแคชในเวลาที่ต้องการ
// 1. แคชระดับไฟล์  
"use cache";  
export default function Page() {  
  return <div>Cached Page</div>;  
}  
  
// 2. แคชระดับคอมโพเนนต์  
export async function PriceDisplay() {  
  "use cache";  
  const price = await fetchPrice();  
  return <div>${price}</div>;  
}  
  
// 3. แคชระดับฟังก์ชัน  
export async function getData() {  
  "use cache";  
  return await db.query();  
}  

การแคชแบบอิงแท็ก

import { unstable_cacheTag as cacheTag, revalidateTag } from 'next/cache';  
  
// แคชกลุ่มข้อมูลเฉพาะ  
export async function ProductList() {  
  'use cache';  
  cacheTag('products');  
  const products = await fetchProducts();  
  return <div>{products}</div>;  
}  
  
// ทำ invalidation ของแคชเมื่อข้อมูลเปลี่ยน  
export async function addProduct() {  
  'use server';  
  await db.products.add(...);  
  revalidateTag('products');  
}  

Cache profile แบบกำหนดเอง

  • สามารถใช้ unstable_cacheLife เพื่อดึง cache profile ที่กำหนดไว้ใน next.config.js มาใช้ได้
  • ใช้ชื่อ profile ที่ประกาศไว้ในโค้ด (เช่น "blog") เพื่อบังคับใช้นโยบายแคช
import { unstable_cacheLife as cacheLife } from "next/cache";  
  
export async function BlogPosts() {  
  "use cache";  
  cacheLife("blog"); // ใช้ blog cache profile ที่กำหนดไว้ล่วงหน้า  
  return await fetchPosts();  
}  

จุดสำคัญที่อาจถูกมองข้าม

การสร้าง cache key อัตโนมัติ

  • props และ arguments ของคอมโพเนนต์จะถูกรวมใน cache key โดยอัตโนมัติ
  • ค่าที่ serialize ไม่ได้ (เช่น ฟังก์ชัน) จะถูกจัดการในรูปแบบ "immutable reference"
export async function UserCard({ id, onDelete }) {  
  "use cache";  
  // id จะถูกรวมใน cache key  
  // onDelete จะถูกส่งต่อ แต่ไม่มีผลต่อการแคช  
  const user = await fetchUser(id);  
  return <div onClick={onDelete}>{user.name}</div>;  
}  

การผสมคอนเทนต์แบบ dynamic กับคอนเทนต์ที่แคช

  • สามารถส่งคอนเทนต์แบบ dynamic เป็น children ภายในคอนเทนต์ที่แคชไว้เพื่อใช้งานร่วมกันได้
  • สามารถกำหนดอาร์เรย์ของ cacheTag เพื่อใช้และทำ invalidation หลายแท็กพร้อมกันได้
export async function CachedWrapper({ children }) {  
  "use cache";  
  const header = await fetchHeader();  
  return (  
    <div>  
      <h1>{header}</h1>  
      {children} {/* คอนเทนต์แบบ dynamic จะยังคงเป็น dynamic */}  
    </div>  
  );  
}  
export async function ProductPage({ id }) {  
  "use cache";  
  cacheTag(["products", `product-${id}`, "featured"]);  
  // ทำ invalidation ได้ด้วยแท็กใดก็ได้ในกลุ่มนี้  
}  

ลำดับชั้นของการแคช

  • หากประกาศ "use cache" ที่ระดับบนสุด พื้นที่ทั้งหมดส่วนนั้นจะถูกแคช
  • สามารถแยกบางส่วนออกจากพื้นที่แคชได้ (เช่น section แบบ dynamic ที่ใช้ Suspense)
"use cache";  
export default async function Page() {  
  return (  
    <div>  
      <CachedHeader />  
      <div>  
        <Suspense fallback={<Loading />}>  
          <DynamicFeed /> {/* คอนเทนต์แบบ dynamic */}  
        </Suspense>  
      </div>  
    </div>  
  );  
}  

ความปลอดภัยของชนิดข้อมูล

  • สามารถจัดการสตริงอย่าง cache key และ cache profile เป็นค่าคงที่เพื่อลดการใช้ magic string
  • หากใช้แนวทางสร้างแท็กแบบเดียวกับแพตเทิร์นของ React Query จะสะดวกมาก
// จัดการคีย์ของ cache profile เป็นค่าคงที่  
export const CACHE_LIFE_KEYS = {  
  blog: "blog",  
} as const;  
  
const config = {  
  experimental: {  
    cacheLife: {  
      [CACHE_LIFE_KEYS.blog]: {  
        stale: 3600,  
        revalidate: 900,  
        expire: 86400,  
      },  
    },  
  },  
};  

วิธีจัดการ cache tag อย่างมีประสิทธิภาพ

  • ประยุกต์ใช้แพตเทิร์น tag factory สไตล์ React Query
export const CACHE_TAGS = {  
  blog: {  
    all: ["blog"] as const,  
    list: () => [...CACHE_TAGS.blog.all, "list"] as const,  
    post: (id: string) => [...CACHE_TAGS.blog.all, "post", id] as const,  
    comments: (postId: string) =>  
      [...CACHE_TAGS.blog.all, "post", postId, "comments"] as const,  
  },  
} as const;  
  
// ตั้งค่า caching tag  
function tagCache(tags: string[]) {  
  cacheTag(...tags);  
}  
  
// ตัวอย่างการใช้งาน  
export async function BlogList() {  
  "use cache";  
  tagCache(CACHE_TAGS.blog.list());  
}  

3 ความคิดเห็น

 
schang124 2025-03-03

ดูเหมือนว่าควรใช้ framework อย่าง Next.js หรือ Remix เฉพาะในกรณีที่ SEO สำคัญจนจำเป็นต้องใช้ SSR เท่านั้น

โดยเฉพาะกับบริการที่ SEO ไม่สำคัญ เช่น ผลิตภัณฑ์ธุรกิจแบบ B2B หรือ back office ผมคิดว่าจำเป็นต้องพิจารณาอย่างรอบคอบก่อนนำ Next.js มาใช้ เพราะอินเทอร์เฟซหรือความซับซ้อนที่ Next.js บังคับใช้อาจลดประสิทธิภาพการพัฒนาได้

โดยส่วนตัวแล้ว หากไม่จำเป็นต้องใช้ SEO ผมคิดว่า Vite + React ดีกว่ามากทั้งในแง่ประสิทธิภาพการพัฒนาและความยืดหยุ่น

 
[ความคิดเห็นนี้ถูกซ่อน]
 
9vvin 2025-02-25

ตั้งแต่ Next.js 13 เป็นต้นมา มันใช้งานได้ดีขึ้นมาก และช่วงหลังนี้ยิ่งถูกใจผมมากจริง ๆ คิดว่าน่าจะกลายเป็นมาตรฐานโดยพฤตินัยของเทคสแต็กสำหรับการพัฒนาเว็บแบบฟูลสแต็ก