1. Introduction: เข้าใจ Server Cache ของ Next.js
Server Cache คืออะไร?
Server Cache หรือ การเก็บแคชข้อมูลบน Server เป็นเทคนิคที่ช่วยให้แอปพลิเคชันสามารถเก็บสำเนาของข้อมูลหรือผลลัพธ์จากการประมวลผลไว้ในหน่วยความจำหรือที่จัดเก็บ เพื่อสามารถนำมาใช้ซ้ำได้โดยไม่ต้องประมวลผลใหม่ทุกครั้ง
ในบริบทของ Next.js, Server Cache มีบทบาทสำคัญมากในการเพิ่มความเร็วของแอปพลิเคชันและลดภาระของ Server โดยเฉพาะในการ Deploy แอปพลิเคชันเว็บใหม่ๆ ซึ่งการเข้าใจและจัดการ Cache อย่างถูกต้องจะช่วยป้องกันปัญหาที่เกิดขึ้นเมื่อมีการ Deploy แอปใหม่
ประโยชน์ของ Server Cache
- Performance: ลดเวลาในการโหลดหน้าเว็บ
- Scalability: ลดภาระของ Server และ Database
- User Experience: ให้ประสบการณ์ผู้ใช้ที่รวดเร็วขึ้น
- Cost: ลดค่าใช้จ่ายในการประมวลผล
ปัญหา Cache ที่พบบ่อย
- Old Assets ค้างใน Browser
- Stale Data บนหน้าเว็บ
- CDN Cache ไม่ถูกปรับปรุง
- Cache Busting ไม่ทำงาน
Key Insight
"ไม่ใช่ทุกปัญหาที่ดูเหมือนจะเป็น Bug จริงๆ ปัญหาบางอย่างเกิดจากการตั้งค่า Infrastructure และ Configuration รอบๆ โค้ด" - การเข้าใจระบบ Cache เป็นสิ่งสำคัญที่สุด
2. Next.js Four-Tier Caching System
Next.js มีระบบ Caching ที่ซับซ้อนและมีประสิทธิภาพสูงโดยใช้ 4 ชั้นหลัก ที่ทำงานร่วมกันเพื่อเพิ่มความเร็วและปรับปรุงประสิทธิภาพของแอปพลิเคชัน
| Cache Type | Mechanism | Location | Purpose | Duration |
|---|---|---|---|---|
| Request Memoization | Return values of functions | Server | Re-use data in a single render | Per-request |
| Data Cache | Fetched data | Server | Share data across users | Persistent |
| Full Route Cache | HTML & RSC Payload | Server | Speed up page rendering | Persistent |
| Router Cache | RSC Payload | Client | Speed up navigation | Session/Time |
1. Request Memoization
การเก็บค่า return ของฟังก์ชันไว้ในหน่วยความจำระหว่างการ render หนึ่งครั้ง ฟีเจอร์นี้ทำงานโดยอัตโนมัติใน Next.js
async function fetchUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`)
return res.json()
}
// First call - cache MISS
const user1 = await fetchUser('123')
// Second call - cache HIT
const user2 = await fetchUser('123')
2. Data Cache
การเก็บข้อมูลที่ fetch ไว้อย่างถาวรและสามารถใช้ร่วมกันระหว่างผู้ใช้หลายคนและ deployments
export default async function Page() {
const data = await fetch('https://...', {
cache: 'force-cache',
next: { revalidate: 3600 }
})
}
3. Full Route Cache
การเก็บ HTML และ React Server Component (RSC) Payload สำหรับเส้นทางทั้งหมด ช่วยให้หน้าเว็บโหลดได้เร็วขึ้น
const Product = async () =gt; {
const products = await getProducts()
return (
<div>
<h1>Products</h1>
<ul>{products.map(p =gt; (<li>{p.title}</li>))}</ul>
</div>
)
}
4. Router Cache
การเก็บ RSC Payload ของแต่ละ route segment ไว้ใน memory ของ browser เพื่อให้การนำทางเร็วขึ้น
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
// Handled automatically by Next.js
return (<Link href="/about">About</Link>)
}
Cache Interactions
การเข้าใจว่า Cache แต่ละชั้นทำงานร่วมกันอย่างไรเป็นสิ่งสำคัญในการแก้ไขปัญหา Cache
| Interaction | Effect |
|---|---|
| Invalidate Data Cache | ✓ Invalidates Full Route Cache |
| Invalidate Full Route Cache | ✗ Does NOT affect Data Cache |
| revalidatePath/Tag in Server Action | ✓ Invalidates both Data & Router Cache |
| Revalidate Data in Route Handler | ✗ Does NOT affect Router Cache immediately |
3. ปัญหา Cache ที่พบบ่อยระหว่าง Deployment
กรณีศึกษา: ปัญหา Cache จริง
หนึ่งในปัญหาที่พบบ่อยที่สุดคือ หลังจาก deploy แอปใหม่ เว็บไซต์ไม่โหลดข้อมูลล่าสุด หรือไฟล์ JavaScript ไฟล์เก่าค้างอยู่ใน browser ทำให้เกิด error หรือแอปใช้งานไม่ได้
Symptom:
- Application โหลดไม่ได้ใน browser ปกติ
- โหลดได้ปกติใน Incognito mode
- ไฟล์ JS served with 304 Not Modified
- Network requests ดึงไฟล์เก่าจาก cache
1. Old Assets in Browser
Browser ยังคงใช้ JavaScript, CSS หรือ image files เก่าที่ cache ไว้ แม้จะ deploy แอปใหม่แล้ว
2. Stale Data
Data ที่ได้จาก API หรือDatabase ยังเป็นข้อมูลเก่าแม้จะมีการอัปเดตข้อมูลแล้ว
3. CDN Cache Issues
CDN ยังคง serve ไฟล์เก่าหรือ content เก่าที่ cache ไว้จาก deployment ก่อนหน้า
Checklist ตรวจสอบ Cache Issues
Development:
Production:
4. Solutions & Best Practices
กลยุทธ์ Cache Busting
Cache Busting คือเทคนิคที่ใช้บังคับให้ browser หรือ CDN โหลดไฟล์เวอร์ชันล่าสุดแทนที่จะใช้ไฟล์ที่ cache ไว้ มีหลายวิธีในการทำ cache busting
Method 1: Build ID / Versioning
ใช้ file name hash หรือ version number เพื่อบ่งชี้เวอร์ชัน
// next.config.js
module.exports = {
generateBuildId: async () =gt; 'v' + Date.now().toString(16),
}
Method 2: Cache-Control Headers
กำหนด header เพื่อควบคุมระยะเวลา cache
// Static assets
Cache-Control: public, max-age=31536000, immutable
// Dynamic content
Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate
Method 3: Query Parameters
เพิ่ม version query parameter ลงใน URL
<script src="/app.js?v=1.2.3"></script>
<link rel="stylesheet" href="/styles.css?cache-bust=12345"/>
Method 4: ReactQuery SWR
ใช้ stale-while-revalidate pattern
const { data } = useSWR('/api/data', {
revalidateOnFocus: false,
refreshInterval: 60000, // 60s
})
next.config.js Configuration
// Configure cache settings for all routes
module.exports = {
headers: async () =gt; [
{
source: '/(.*)',
headers: [
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-XSS-Protection',
value: '1; mode=block',
},
],
},
],
images: {
domains: ['images.example.com'],
formats: ['image/avif', 'image/webp'],
},
}
vercel.json Configuration
{
"headers": [
{
"source": "/api/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "no-cache, no-store, must-revalidate" }
]
},
{
"source": "/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
]
}
],
"rewrites": [
{ "source": "/(.*)", "destination": "/api/proxy" }
]
}
Best Practices Summary
Explicit Headers
Always configure Cache-Control headers explicitly
CDN Purge
Automate CDN cache purging during deployment
Version Hashing
Use file name hash for immutable assets
Revalidate Data
Set appropriate revalidation periods
Test Both Modes
Test in both normal and incognito modes
Monitor Logs
Regularly monitor cache headers in production
ISR Strategy
Use Incremental Static Regeneration for dynamic content
Protect Sensitive
Never cache sensitive user data
Use Tags
Use cache tags for fine-grained revalidation
5. Code Examples & Use Cases
Example 1: API Data Caching
ตัวอย่างการใช้ Data Cache กับ API data โดยใช้ cache tag
import { NextResponse } from 'next/server'
import { cacheTag } from 'next/cache'
export async function GET() {
cacheTag('products')
const res = await fetch(
'https://api.example.com/products',
{
next: { revalidate: 3600 }
}
)
const data = await res.json()
return NextResponse.json(data)
}
ใช้ cacheTag เพื่อกำหนด tag ให้ data และใช้ revalidate: 3600 เพื่อ revalidate ทุก 1 ชั่วโมง
Example 2: On-Demand Revalidation
ตัวอย่างการใช้ on-demand revalidation กับ Server Actions
'use server'
import { revalidateTag } from 'next/cache'
import { db } from '@/lib/db'
export async function createProduct(formData: FormData) {
// Create product in database
await db.product.create({
data: {
name: formData.get('name'),
price: Number(formData.get('price')),
}
})
// Invalidate cache for products
revalidateTag('products')
revalidatePath('/products')
redirect('/products')
}
After creating a new product, we invalidate both the cache tag and the route path to ensure fresh data
Note:
Use revalidateTag('products', 'max') for stale-while-revalidate behavior instead of immediate invalidation
Example 3: Incremental Static Regeneration (ISR)
ตัวอย่างการใช้ ISR เพื่อ update หน้าเว็บโดยไม่ต้อง rebuild ทั้งหมด
import { notFound } from 'next/navigation'
import { cache } from 'react'
const getProduct = cache(async (slug: string) =gt; {
const res = await fetch(
`https://api.example.com/products/${slug}`,
{
next: { tags: ['product', `product-${slug}`] }
}
)
if (!res.ok) return null
return res.json()
})
export default async function ProductPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const product = await getProduct(slug)
if (!product) return notFound()
return (
<div>
<h1>{product.name}</h1>
<p>Price: ${product.price}</p>
</div>
)
}
// Generate static paths at build time
export async function generateStaticParams() {
const products = await fetch('https://api.example.com/products')
.then(r =gt; r.json())
return products.slice(0, 100).map(p =gt; ({ slug: p.slug }))
}
Mechanism:
- 100 products ถูก generate ที่ build time
- Product ที่ยังไม่ได้ generate จะถูกสร้างทันทีที่มีการ request
- Data จะถูก revalidate ทุกครั้งที่มี request (stale-while-revalidate)
Example 4: Automate CDN Purge on Deploy
ตัวอย่าง script สำหรับ purge CDN cache หลัง deployment
{
"scripts": {
"build": "next build",
"deploy": "npm run build && vercel --prod && npm run purge-cache",
"purge-cache": "node scripts/purge-cache.js"
}
}
// scripts/purge-cache.js
const { execSync } = require('child_process')
const project_id = 'your-project-id'
const token = 'YOUR_VERCEL_API_TOKEN'
// Invalidate all caches
execSync(`curl -X POST https://api.vercel.com/v10/deployments/${project_id}/cache \
-H "Authorization: Bearer ${token}" \
-H "Content-Type: application/json" \
-d '{"cache": "invalidate_all"}'`)
console.log('✓ CDN cache purged successfully!')
แนะนำให้ใช้ GitHub Actions หรือ CI/CD pipeline เพื่อ automate การ purge cache หลัง deployment
6. Troubleshooting: Debug Cache Issues
Debugging Checklist
Browser DevTools
Cache-Control and ETag headers
Server/Environment
.next directory size (clear if needed)
buildId is different after rebuild
next start
Problem: Old Assets Load
Symptoms:
- Site doesn't load in normal tab
- Works in Incognito mode
- 304 Not Modified on JS files
Solution:
// 1. SetCache-Control headers
res.setHeader('Cache-Control',
'no-store, no-cache, must-revalidate, proxy-revalidate')
// 2. Clear browser cache
// 3. Add cache buster query param
<script src="/app.js?v=1.2.3"></script>
Problem: Stale Data Shows
Symptoms:
- Content not updating after changes
- Data not reflecting recent changes
- ISR pages not revalidating
Solution:
// 1. Use on-demand revalidation
revalidateTag('content')
revalidatePath('/articles')
// 2. Set shorter revalidation time
const res = await fetch(url, {
next: { revalidate: 60 } // 1 minute
})
Tools for Debugging
Next.js Dev Tools
Enable strict mode and check cache status
next dev --experimental-strict
Vercel Dashboard
Monitor cache hits/misses and deployment status
vercel deploy --prod --prod
Cache Purge API
Programmatically purge caches
POST /v10/deployments/:id/cache
7. คำศัพท์ภาษาไทย (Thai Terminology)
| English Term | Thai Translation | Description |
|---|---|---|
| Server Cache | แคชของ Server | การเก็บข้อมูลหรือผลลัพธ์ที่ได้จากการประมวลผลไว้ในหน่วยความจำของ Server |
| Cache Busting | การขจัดแคช | เทคนิคที่ใช้บังคับให้ browser โหลดไฟล์เวอร์ชันล่าสุดแทนไฟล์ที่ cache ไว้ |
| Revalidation | การตรวจสอบและอัปเดตแคช | การตรวจสอบว่าข้อมูลในแคชยังใช้งานได้หรือไม่ และอัปเดตข้อมูลใหม่เมื่อจำเป็น |
| On-Demand Revalidation | การตรวจสอบและอัปเดตแคชแบบเรียกใช้ | การเรียกใช้การตรวจสอบและอัปเดตแคชเมื่อมีเหตุการณ์เฉพาะเกิดขึ้น |
| Time-Based Revalidation | การตรวจสอบและอัปเดตแคชตามเวลา | การอัปเดตแคชโดยอัตโนมัติทุกช่วงเวลาที่กำหนด |
| CDN Cache | แคชของ CDN | การเก็บสำเนาของไฟล์และข้อมูลไว้ที่ server ใกล้กับผู้ใช้มากที่สุด |
| Cache Purge | การลบแคช | การลบข้อมูลที่ cache ไว้ออกทั้งหมดเพื่อบังคับให้โหลดใหม่ |
| Stale Data | ข้อมูลที่ไม่อัปเดต | ข้อมูลที่อยู่ในแคชแต่ไม่ได้อัปเดตตามข้อมูลล่าสุด |
| Cache Tag | แทแท็กของแคช | string ที่ใช้ระบุและจัดกลุ่มข้อมูลในแคชเพื่อให้สามารถลบได้ตาม tag |
| ISR (Incremental Static Regeneration) | การสร้างสถิติแบบค่อยเป็นค่อยไป | เทคนิคที่อนุญาตให้สร้างและอัปเดตหน้าเว็บแบบ static แบบเรียลไทม์โดยไม่ต้อง rebuild ทั้งหมด |
| Cache-Control Headers | headers ควบคุมแคช | HTTP headers ที่ใช้กำหนดว่าใครสามารถเก็บ cache ได้และเก็บได้นานแค่ไหน |
| Build ID | รหัส build | identifier ที่ Next.js ใช้ระบุแต่ละ deployment |
คำศัพท์ที่สำคัญอื่นๆ
การ render หน้าเว็บบน Server ทุกครั้งที่มี request
การ generate HTML ที่ build time และ serve ซ้ำ
การ render หน้าเว็บบน Browser โดยใช้ JavaScript
เมื่อข้อมูลที่ต้องการพบใน cache (สำเร็จ)
เมื่อข้อมูลที่ต้องการไม่พบใน cache (ต้อง fetch ใหม่)
ระยะเวลาที่ข้อมูลใน cache ถูกใช้งานได้ก่อนต้อง revalidate