๐ Next.js ์ฌํ 3์ฅ: Cache Invalidation โ ํ์์ ๊นจ๋ถ์๋ ์ฒ ํด
๐ ๊ฐ์
revalidatePath, revalidateTag, on-demand ISR๋ก ์บ์๋ฅผ ์ ๋ฐํ๊ฒ ๋ฌดํจํํ๋ ๋ฐฉ๋ฒ์ ๋ค๋ฃน๋๋ค.
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค ์ ์์์ผ ํ๋๊ฐ
- ๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
- ๐งฉ
revalidatePath: ๋ฌด์ํ๊ฒ ๋๋ผ๋ก ๋ด๋ ค์ฐ๊ธฐ ๐ข - ๐ฑ
revalidateTag: ์ฐ์ํ๊ณ ์ ๋ฐํ ์ ๊ฒฉ์ด ๐ก - ๐ก๏ธ
router.refresh(): ์ ์ ๋ธ๋ผ์ฐ์ ์์ด ์ง์ฐ๊ธฐ ๐ด - ๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
- ๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
- ๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
- ๐ ๋ ์์๋ณด๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 15๋ถ (์ ์ฒด) / ํต์ฌ ํํธ๋ง: 8๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ์์ฒ (์ ์
): "๊ฒ์๋ฌผ ์์ ๋ฒํผ ๋๋ฅด๊ณ ์๋ฒ API๋ก DB ์
๋ฐ์ดํธ๋ฅผ ์๊ณ ๋์
window.location.reload()๋ก ์๋ก๊ณ ์นจ์ ๊ฐ๊ฒผ๋๋ฐ, ํ๋ฉด์ ๊ณ์ ๋๊ฐ์ ์์ ๊ธ์จ๋ง ๋ ์! Next.js ์๋ฒ์ ์๊ตฌ ์บ์(Data Cache)๋ฅผ ๋ชป ๋ถ์๊ณ ์๋ค๋ ๊ฑด ์๊ฒ ์ด์. ์ด๋ป๊ฒ ๋ถ์์ฃ ?" - ์ํธ(๋ฆฌ๋): "์์ฒ ๋... ๋ธ๋ผ์ฐ์ ์์ '์๋ก๊ณ ์นจ' ๋ฐฑ๋ ๋๋ฌ๋ด์ผ, ๋นํ ์(์๋ฒ)์์ ์์ํ ์ ๋ ์ผ์์กฐ๊ฐ์ ๊นจ์ง์ง ์์์! ์๋ฒ ์ฝ์ด ๊น์์ด ์ ๊ทผํด์
revalidatePath๋revalidateTag๋ผ๋ ํญํ ๋ช ๋ น์ ๋ด๋ ค์ผ๋ง ๋น๋ก์ ์ผ์๋ฒฝ์ด ๋ฌด๋์ง๋ค๊ณ ์!"
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
์ ์ ์บ์ ๋ฌดํจํ์ ํ์์ฑ โ ๊ฒฝ๋ก ๋จ์ ํ๊ดด โ ํ๊ทธ ๊ธฐ๋ฐ ํ๊ดด โ ์ค๋ฌด ์ํคํ
์ฒ
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
- CMS/์ด๋๋ฏผ ์ฌ์ดํธ์์ [์ ์ฅ] ๋ฒํผ์ด ๋๋ฆฌ๋ ์ฆ์, ํผ๋ธ๋ฆญ ์ ์ ๋ค์ด ๋ณด๋ ๋ฉ์ธ ์ ์ ์น์ฌ์ดํธ์ ํน์ ์บ์๋ง ์ฐ์ํ๊ฒ ์ด๊ธฐํํด์ฃผ๋ ์นํ (Webhook) ์๋ฒ API๋ฅผ ์ค๊ณํ ์ ์๋ค.
- ๋ฉ์ฒญํ
revalidatePath๋์ , ์ฌ๋ฌ ํ์ด์ง์ ์ ์กฐ์ง์ฒ๋ผ ํฉ์ด์ง ๋์ผํ API ๊ฒฐ๊ณผ๋ฅผ ํ ๋ฐฉ์ ๋ถ์๋จ๋ฆฌ๋tag๊ธฐ๋ฐ Invalidation์ ์ค์ ํ ์ ์๋ค.
๐ค ์ ์์์ผ ํ๋๊ฐ
Next.js App Router ์ํคํ ์ฒ๋ ๊ทน๋จ์ ์ธ ์บ์ฑ(Caching)์ ์ ์ ๋ก ๋ง๋ค์ด์ก์ด. ์ฌํ 1/2์ฅ์์ ์ด๋ป๊ฒ ํ๋ฉด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ฅ ํจ์จ์ ์ผ๋ก ํ์ ์ผ๋ ค๋ฒ๋ฆด ์ ์์๊น๋ฅผ ๋ฐฐ์ ๋ค๋ฉด, ์ง๊ธ๋ถํฐ๋ ๊ทธ ์ผ์์ "๋ด๊ฐ ์ํ ๋, ์ํ๋ ๋ถ๋ถ๋ง" ๋ฐ์ด ๋ด๋ ๋ฒ(Invalidation)์ ๋ฐฐ์์ผ ํด.
๋ฐ์ดํฐ ์์ฑ(C), ์์ (U), ์ญ์ (D) ์ก์ ์ด ์ผ์ด๋ฌ์ ๋, ๊ด๋ จ ํ๋ฉด์ ์บ์ ๊ป์ง์ ์ฐข์ง ๋ชปํ๋ฉด ์ฑ์ ์์๊ฐ์ ๋ก์ ์ ๋ณด(Stale Data) ๋ง ๋ฑ์ด๋ด๋ ๊นกํต์ด ๋ผ. ํ๋ก ํธ์๋์ ์ฒด๊ฐ ํ๋ฆฌํฐ์ 90%๋ ์ด "์์ ํ ๊ฒ ๋ฐ๋ก๋ฐ๋ก ํ๋ฉด์ ๋ฐ์๋๋๊ฐ?" ์์ ํ๊ฐ๋ฆ ๋์ง. ์ด๊ฒ์ด ๋ฐ๋ก ์๋์ด์ ์บ์ฑ ํต์ ๋ ฅ์ด์ผ.
๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
๐ง 5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด? (์น ํ ์ง์ฐ๊ธฐ)
์ ์๋(Next.js)์ด ์น ํ์ ์ค๋ ๊ธ์ ๋ฉ๋ด(Data Cache)๋ฅผ ์๋ฉ ์ ์ด๋จ์ด. ์์ด๋ค์ ๊ทธ๊ฑธ ๋ณด๊ณ ํํธํ์ง.
์์ฒ ์ด๊ฐ ๊ต๋ฌด์ค์ ๊ฐ์ ๊ธ์ ๋ฉ๋ด๋ฅผ "์ ์ก๋ณถ์"์์ "๋๊น์ค"๋ก ๋ฐ๊ฟจ์ด(DB ์ ๋ฐ์ดํธ).โ ๊ณผ๊ฑฐ (๋ธ๋ผ์ฐ์ ์๋ก๊ณ ์นจ)
์์ฒ ์ด๊ฐ ๋ณต๋์์ ์๋ฌด๋ฆฌ "์๋ก๊ณ ์นจ! ๋๊น์ค์ผ!" ์๋ฆฌ์ณ๋ด์ผ, ์ ์๋์ ์น ํ์๋ ์ฌ์ ํ ์ ์ก๋ณถ์์ด ์ ํ์์ด.โ ํ์ฌ (
revalidateํญํ ๋ช ๋ น)
์์ฒ ์ด๊ฐ ์ ์๋ ๊ท์ ๋๊ณ "์ ์๋, ์ ๊ธฐ 2๋ฒ์งธ ์ค(๊ฒฝ๋ก/ํ๊ทธ) ๊ธ์ ๋ฉ๋ด ๋ก์์ด์, ์ข ์ง์์ฃผ์ธ์!" ๋ผ๊ณ ์๋ฒ ๋ด๋ถ ๋ช ๋ น๋ง์ผ๋ก ์์ญ์ฌ.
์ ์๋์ด ๊ทธ์ ์ผ "์ด์ฟ !" ํ๊ณ ์ ์ก๋ณถ์์ ์ง์๋ฒ๋ ค(Cache Purge). ๋ค์ ์์ด๊ฐ ์น ํ์ ๋ดค์ ๋ ์ ์ก์ด ์์ผ๋ ์๋ฆฌ์ฌํํ ๋ฐ์ด๊ฐ "์ ๋ฉ๋ด ์ค!" ํ๊ณ ๋๊น์ค ๋ฐ์ดํฐ๋ฅผ ์ง์ ์ผ๋ก ์ ๋ฐ์ดํธํ๊ฒ ๋๋ ๊ฑฐ์ผ.
๐งฉ revalidatePath: ๋ฌด์ํ๊ฒ ๋๋ผ๋ก ๋ด๋ ค์ฐ๊ธฐ ๐ข
์ ์ผ ์ง๊ด์ ์ด๊ณ ๋ฌด์(?)ํ ํ๊ดด ๋ฐฉ์์ด์ผ.
"ํน์ ์ฃผ์(๊ฒฝ๋ก)์ ์ฐ๊ด๋ ๋ ๋๋ง ํ์๊ณผ ๋ฐ์ดํฐ ์บ์๋ฅผ ๋ชจ์กฐ๋ฆฌ ๋ค ๋ ๋ ค๋ฒ๋ ค๋ผ!"
์ฌ์ฉ๋ฒ
์ฃผ๋ก ๊ฒ์๊ธ ์์ฑ/์์ API(Route Handler app/api/... ๋ด๋ถ)๋, ๋ค์ ์ฅ์์ ๋ฐฐ์ธ ์๋ฒ ์ก์
๋จ์์ ์ฌ์ฉ ๋ผ.
import { revalidatePath } from 'next/cache'
// ์ ์ ๊ฐ ์ํ์ ๊ตฌ๋งคํ ์งํ ์คํ๋๋ ์๋ฒ ๋ก์ง์ด๋ผ ๊ฐ์
export async function POST() {
await db.purchaseItem(...) // 1. DB ์
๋ฐ์ดํธ (์๋ ๊ฐ์)
// 2. ๋๋ผ๋ก ๊ฒฝ๋ก ๊ฐํ!
// "/shop" ํ์ด์ง์ ํต์งธ๋ก ๊ตฌ์์ง ํ์ ํ์ผ(HTML)๊ณผ ๊ทธ ์์ ๋ชจ๋ Data Cache๋ฅผ ๋ฐ์ด๋ธ๋ค!
revalidatePath('/shop')
return Response.json({ success: true })
}์ฅ๋จ์
- ์ฅ์ : ์์ฒญ๋๊ฒ ์ง๊ด์ ์. "๊ทธ๋ฅ ๊ทธ ํ์ด์ง ๋ก์ ๊ฑฐ ๋ค ์น ์ง์!"
- ๋จ์ : ๋ง์ฝ
/shopํ์ด์ง ์์ ์๋ฌด ์๋ชป ์๋(์ ๋ฐ๋) ๊ณต์ง์ฌํญ์ด๋ ๋ฐฐ๋ ์บ์ ๋ฐ์ดํฐ๊น์ง ๋ชจ์กฐ๋ฆฌ ๊ฐ์ด ๋ ์๊ฐ ๋ฒ๋ฆผ.
๊ทธ๋ฆฌ๊ณ ๋ ์น๋ช ์ ์ธ ๊ฑด,/shop์๋ ์ํ ๋ชฉ๋ก์ด ์๊ณ/mypage/recent์๋ ์ํ ๋ชฉ๋ก์ด ์๋ค๋ฉด? ๋๋ผ๋ฅผ ๋ค๊ณ ๋ค๋๋ฉฐ ์ ๊ฒฝ๋ก๋ค์ ์ผ์ผ์ด ์ ๋ถ ๋ ธ๊ฐ๋ค๋ก ์ ์ด์ ํ๊ดด๋ช ๋ น์ ๋ด๋ ค์ผ ํด.
๐ฑ revalidateTag: ์ฐ์ํ๊ณ ์ ๋ฐํ ์ ๊ฒฉ์ด ๐ก
Next.js ์ฝ์ด ํ์ด ๊ฐ์ฅ ๋ฏธ๋ ์ฐ์ํ๊ณ ์๋ฒฝํ ํด๊ฒฐ์ฑ
์ด์ผ.
"์๋ฌด๋ฆฌ ์ฌ๋ฌ ํ์ผ๊ณผ ์ฌ๋ฌ ์ฃผ์์ ํฉ์ด์ ธ์์ด๋, ๋๊ฐ์ '์ด๋ฆํ(Tag)'๋ฅผ ๋ฌ๊ณ ์๋ ์บ์๋ค๋ง ๊ณจ๋ผ์ ํ์
์ผ๋ก ์ ๊ฒฉํด ํญํํ๋ค!"
1) ๋ช ์ฐฐ ๋ถ์ด๊ธฐ (๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ๋)
์ด๊ฑด page.tsx๋ ์ด๋๋ fetch๋ฅผ ํ๋ ๋
์์๊ฒ ๊ผฌ๋ฆฌํ๋ฅผ ๋ฌ์์ฃผ๋ ๊ฑฐ์ผ.
// app/shop/page.tsx
const res = await fetch('https://api.my.com/bestseller', {
next: { tags: ['bestseller-list'] } // ๐ท๏ธ ์ด ํต์ ๊ฒฐ๊ณผ๋ฌผ ๋ฉ์ด๋ฆฌ ์บ์์ 'bestseller-list'๋ผ๋ ์ด๋งํ๋ฅผ ๋ถ์ธ๋ค!
})// app/mypage/page.tsx
const res2 = await fetch('https://api.my.com/bestseller?limit=3', {
next: { tags: ['bestseller-list'] } // ๐ท๏ธ ์์ ๋ค๋ฅธ ํ์ด์ง์ธ๋ฐ ์ฌ๊ธฐ๋ ๊ฐ์ ์ด๋งํ๋ฅผ ๋ถ์ธ๋ค!
})2) ์ ๊ฒฉ ํ! (๋ฐ์ดํฐ๊ฐ ์์ ๋ ๋)
์ด๋๋ฏผ ์๋ฒ์์ ๋ฒ ์คํธ์ ๋ฌ ์ํ 1๊ฐ๋ฅผ ๊ฐ์ ๋ก ๊ต์ฒด(์์ )ํ์ด. ์ด๋ ์นํ (Webhook) ์๋ฒ์์ ๋จ ํ ๋ฐ์ ์ด์๋ง ์๋ฉด ๋ผ.
import { revalidateTag } from 'next/cache'
export async function POST() {
await db.updateBestseller(...)
// ๐ซ ์ ๊ฒฉ ํ!
// "/shop" ์ด๊ณ "/mypage" ์ด๊ณ ๋๋ฐ์ด๊ณ ๋ฌป์ง๋ ๋ฐ์ง์ง๋ ์๊ณ
// Next.js ์ ์ญ์์ 'bestseller-list' ๋ช
์ฐฐ์ด ๋ถ์ ๋ชจ๋ ์ผ์ ์กฐ๊ฐ๋ง ์น๋ค ์ฐพ์๋ด ํ๊ดดํจ!
revalidateTag('bestseller-list')
return Response.json({ success: true })
}์ด๊ฒ ๋ฐ๋ก ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ(MSA) ๊ธ์ ์ฐ์ํ ์บ์ ๋ถ์ฐ ํต์ ์ (Cache Orchestration)์ด์ผ.
๐ก๏ธ router.refresh(): ์ ์ ๋ธ๋ผ์ฐ์ ์์ด ์ง์ฐ๊ธฐ ๐ด
์ ๋ ๊ฐ์ ํญํ ๊ฒฐ์ฌ์ (Path, Tag)์ ์ ๋ถ ์๋ฒ(Node.js ๋จ) ์์๋ง ๋ฐ๋ ๊ฐ๋ฅํ ๋ฌด๊ธฐ๋ค์ด์ผ.
ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ(use client) ๋ด๋ถ์ onClick ์ด๋ฒคํธ์์๋ ์ ํญํ ์ค์์น๋ฅผ ํจ๋ถ๋ก ๋๋ฅผ ์๊ฐ ์์ด! (๋ณด์ ์ ์ฑ
์)
ํด๋ผ์ด์ธํธ ๋จ์์ ์ง์ "๋ ๋ฐฉ๊ธ ๊ธ ์ง์ ๋๋ฐ, ํ๋ฉด ์ข ์ฆ์ ๋ค์ ๊ทธ๋ ค์ฃผ๋ผ!" ๋ผ๊ณ ๋ช
๋ นํ๊ณ ์ถ๋ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น?
๋ฐ๋ก Next.js์ ํด๋ผ์ด์ธํธ ๋ผ์ฐํฐ ํ
์ ๋ฌ๋ฆฐ refresh() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ ๊ฑฐ์ผ.
'use client'
import { useRouter } from 'next/navigation'
export default function DeleteButton({ id }) {
const router = useRouter()
const handleDelete = async () => {
// 1. ์ปดํฌ๋ํธ ๋ด๋ถ์์ ๋ฐฑ์๋ ์ญ์ API ํธ์ถ
await fetch(`/api/posts/${id}`, { method: 'DELETE' });
// 2. ๋ธ๋ผ์ฐ์ ์ผ! ๋ค ๋ฉ๋ชจ๋ฆฌ(Router Cache)์ ๋์๋ ๋ก์ HTML ๊ป๋ฐ๊ธฐ๋ฅผ ๋ฒ๋ ค!
// ๊ทธ๋ฆฌ๊ณ ๊ฐ์ ๋ก ํ๋ฉด ๊น๋นก์(F5) ์์ด ๋ฐฑ๊ทธ๋ผ์ด๋๋ก ์๋ฒ๊ฐ ์ฃผ๋ ์ต์ ๋ ๋๋ง ๋ทฐ๋ฅผ ๋ค์ ๊ธ์ด์!
router.refresh();
}
return <button onClick={handleDelete}>๊ธ ์ญ์ </button>
}๐ฅ ์ฃผ์ํ ์ !
router.refresh()๋ ์ฌํ 1์ฅ์์ ๋ฐฐ์ด [๊ฐ์ฅ ์ฒซ ๋ฒ์งธ ๋ฐฉ์ด๋ง์ธ ๋ธ๋ผ์ฐ์ ๋จ์ Router Cache] ๋ง์ ๋ถ์ด๋ฒ๋ฆฌ๋ ํ์์ผ!
๋ง์ฝ ๋ฐฑ์๋ ์ญ์ API(DELETE) ์ชฝ์, ๋ณธ์ง์ ์ธ Next.js ์๋ฒ์ ์ผ์์ ๋ถ์๋revalidatePath('/posts')๋ก์ง์ด ์ ๋ค์ด๊ฐ ์๋ค๋ฉด?
refresh()๋ช ๋ น์ ๋ฐฑ๋ ์ณ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์๋ฒ์ HTML์ ๋ค์ ๋ฌ๋ผ๊ณ ์กฐ๋ฅด๋๋ผ๋, ์๋ฒ๋ "์ด? ๋ ์ ๋ฒ ๋ก์ ํ์ ๋ด๊ฐ ๊ฐ๊ณ ์์๋ ๊ฐ์ ธ๋ผ ใ " ํ๊ณ ๋ก์ ํ๋ฉด์ ๋ค์ ์ฃผ๊ฒ ๋๋ฏ๋ก ์์ํ ์ง์ฅ๋๋ฅผ ๋งด๋๊ฒ ๋ผ. ํญ์ ์๋ฒ ๋ฌดํจํ(revalidate) + ํด๋ผ ๋ฌดํจํ(refresh)๋ ์ํด๋ฆญ ํ ๋ชธ ์ธํธ๋ก ๊ฐ์ผ ํด.
๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
์๋ฌ ๋ฉ์์ง๊ฐ ๋จ๋ฉด Ctrl+F๋ก ๊ฒ์ํด๋ด.
โ revalidateTag is not a function ํน์ ์๋ฒ ์๋ฌ
์์ธ: revalidateTag ๋ฑ ์บ์ ํ๊ดด ์ง์์๋ฅผ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ("use client") ์์ญ์์ import ํด์ ๋ฒํผ onClick ํจ์ ์์์ ์ง์ ๋ถ๋ฅด๋ ค๊ณ ์์ ํ์.
ํด๊ฒฐ์ฑ
: ์ด ํ๊ดด ํจ์๋ ๋ณด์์ ์ค๋ก์ง [์๋ฒ ์ก์
(Server Actions)] ์ด๋ Route Handlers(app/api/) ํ๊ฒฝ๊ฐ์ด ์์ ํ Node.js ์์ญ ๋ด๋ถ์์๋ง ์์
(next/cache)ํด์ ๋๋ฆด ์ ์๋ค.
โ ํ๊ทธ ์ ๊ฒฉ์ด์ ์๋๋ฐ๋ ๊ธ์จ๊ฐ ์ ๋ณํด์!
์์ธ: fetch ํต์ ๋ถ์ { next: { tags: ['my-tag'] } } ๊ผฌ๋ฆฌํ๋ฅผ ๋ช
์ํ๋ ๊ฑธ ๊น๋นกํ๊ฑฐ๋ ์คํ๊ฐ ๋ ๊ฒฝ์ฐ.
ํด๊ฒฐ์ฑ
: ์๋ฒ ์บ์ ๋คํ ๋ก๊ทธ๋ฅผ ์ฐ์ด๋ณด๊ณ (logging: { fetches: { fullUrl: true } } config ํ์ฉ), ๋ด๊ฐ ํ๊ฒฉํ ํ๊ทธ ์ด๋ฆํ์ ์ฒ ์๊ฐ ์ ํํ ์ผ์นํ๋์ง ์ ๊ฒํ ๊ฒ.
๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
| ๋ฌดํจํ(๋ถ์๊ธฐ) ์๋จ | ์๋ ์์น | ์ฅ๋จ์ ๋ฐ ์ฌ์ฉ์ฒ |
|---|---|---|
revalidatePath(์ฃผ์) | ์๋ฒ (Node.js) | URL ํ๋ ๊ธฐ๋ฐ์ ์ ๋ฉด ํต์งธ ํ์ ํญํ. ๋จ์ํ์ง๋ง ์ ๋ฐ ํ๊ฒฉ ๋ถ๊ฐ. |
revalidateTag(ํ๊ทธ๋ช
) | ์๋ฒ (Node.js) | ์ด๋ฆํ๊ฐ ๋ถ์ ์บ์๋ค๋ง ์ ์ธ๊ณ ํํธ์ ๋ค์ ธ ๋ฉธ์ข ์ํด. ๊ณ ๋๋ ์ํคํ ์ฒ ํ์ ์ค๊ณ |
router.refresh() | ํด๋ผ์ด์ธํธ (๋ธ๋ผ์ฐ์ ) | ์๋ฒ๊ฐ ๋ฑ์ด๋ด๋ ๋ก์ RSC Payload๋ฅผ ํ์ฌ ๋ธ๋ผ์ฐ์ ํญ์์ ์ง์๋ฒ๋ฆฌ๊ณ UI ์ฌ์์ฒญ ๊ฐฑ์ (ํ๋ฉด ์ ๊น๋นก์) |
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
๋ฐฐ์ ์ผ๋ฉด ํ ๋ฒ ๋นํ์ด์ ํ์ธํด๋ด์ผ ํด.
- ์ํฉ: ์์ฒ ์ด๊ฐ ์ผํ๋ชฐ์ ๋ฉ์ธ ์ํ ๋ชฉ๋ก ํ์ด์ง(
/)์ ๊ฐ์ธ ๊ด์ฌ ์ํ ํ์ด์ง(/mypage/like) 2๊ณณ์ ๋ ธ์ถ๋๊ณ ์๋item_id=10๋ฒ ์ํ์ ๊ฐ๊ฒฉ์ ๊ด๋ฆฌ์ ํ์ด์ง์์ 10๋ง ์์์ 5๋ง ์์ผ๋ก ์์ (UPDATE)ํ๋ API๋ฅผ ์งฐ๋ค. ๋ง์ฝ ์์ฒ ์ด๊ฐrevalidatePath('/')๋๋ผ์ง ํ๋๋ง ํ๊ณ ๋๋๋ค๋ฉด, ๊ณ ๊ฐ C๊ฐ/mypage/likeํ์ด์ง์ ๋ค์ด๊ฐ์ ๋ ์ด ์ํ ๊ฐ๊ฒฉ์ 10๋ง ์์ผ๊น, 5๋ง ์์ผ๊น?
์ ๋ต ๋ฐ ํด์ค:
๊ณ ๊ฐ C๋ ์ฌ์ ํ ๋ก๊ณ ๋น์ผ 10๋ง ์์ง๋ฆฌ ๋ฒ๊ทธ ํ๋ฉด์ ๋ณด๊ฒ ๋๋ค.
revalidatePath('/')๋ ์ค๋ก์ง ๋ฃจํธ(๋ฉ์ธ ํํ์ด์ง) ํ๋์ ์์ธ ์ปดํฌ๋ํธ ํธ๋ฆฌ์ ์ผ์ ์ฅ๋ฒฝ๋ง ๊นจ๋ถ์๊ธฐ ๋๋ฌธ์ด๋ค./mypage/like์ชฝ์ ๋ฌป์ด์๋ ํด๋น ์ํ ์ปดํฌ๋ํธ๋ ์บ์๋ ์ํฅ์ ๋ฐ์ง ์์๋ค.
์ด ์ฐธ์ฌ๋ฅผ ๋ง๊ธฐ ์ํด ์ํ ๊ฐ๊ฒฉ์ ๊ทธ๋ฆฌ๋ ๋ชจ๋fetch๋จ์{ next: { tags: ['item-10'] } }์ฒ๋ผ ๊ณ ์ ํ๊ทธ๋ฅผ ๋ฐ์ ๋ ๋ค, ๊ด๋ฆฌ์ ์ ์ฅ ๋ก์ง์์revalidateTag('item-10')ํ ๋ฐ๋ง ์์ด์ผ ๋ชจ๋ ์์ ์ฒ์ ๊ฐ๊ฒฉ ํ๊ธฐ๊ฐ ์ ์ ๊ฐ๊ฒฉ์ธ 5๋ง ์์ผ๋ก ๊ฐฑ์ ๋ ์ ์๋ค.
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋์ ์ ๋ง ๋ฅ์คํธ์ '์ผ์ ๊นจ๊ธฐ(Invalidation)' ๊ธฐ์ ์ ๋ฐฐ์ฐ๋ฉด์ ๊ฐํ์ ๊ธ์น ๋ชปํ์ด. ์ด์ ๋ง๋ ํ์์ด ์ค๋๊น์ง ๋จ์์์ด ๋นํฉํ๋๋ฐ, ์ํธ ๋ฆฌ๋ ๋์ด ์๋ ค์ฃผ์ '์ ๋ฐ ํ๊ฒฉ(revalidateTag)' ๋๋ถ์ ์ด์ ๋ ๋ด๊ฐ ์ํ๋ ๋ถ๋ถ๋ง ์ฝ ์ง์ด์ ์ต์ ํํ ์ ์๊ฒ ๋์ด!
๐ก ์ค๋์ ๊ตํ: "์บ์๋ฅผ ๋ง๋๋ ๊ฒ๋ณด๋ค ๋ ์ค์ํ ๊ฑด, ์ธ์ ์ด๋ป๊ฒ ๋ถ์ ์ง ๊ฒฐ์ ํ๋ ๊ฒ์ด๋ค.
revalidatePath๋ก ํผ์งํ๊ฒ,revalidateTag๋ก ์ ๊ตํ๊ฒ ์ผ์์ ๊นจ์!"
๊ฐ์ฅ ์ ๊ธฐํ๋ ๊ฑด ๋ธ๋ผ์ฐ์ ์ ๊ธฐ์ต๋ ฅ(router.refresh())๊ณผ ์๋ฒ์ ๊ธฐ์ต๋ ฅ์ ๋์์ ์ปจํธ๋กคํด์ผ ํ๋ค๋ ์ ์ด์์ด. ๋ ์ค ํ๋๋ผ๋ ์ด๊ธ๋๋ฉด ์ ์ ๋ ๋ก์ ํ๋ฉด์ ๋ณด๊ฒ ๋๋ค๋ ์ฌ์ค์ ์ ์ ์ด ๋ฒ์ฉ ๋ค๋๋ผ. ์ค๋ ๋๋ฌด ์ง์คํด์ ์ผํ๋๋ ์ด๊นจ๊ฐ ๋ป๊ทผํ๋ค. ํด๊ทผํ๊ณ ์ง ๊ฐ์ ์์ํ ๋งฅ์ฃผ ํ ์บ ๋ง์๋ฉฐ ์ค๋ ๋ฐฐ์ด '์บ์ ํญํ' ๋ก์ง์ ๋ค์ ํ๋ฒ ๋จธ๋ฆฟ์์ผ๋ก ๊ทธ๋ ค๋ด์ผ๊ฒ ์ด. ๋ด์ผ์ ๋ ์ฐ์ํ ๋ฌดํจํ ์ฝ๋๋ฅผ ์ง๋ ๊ฐ๋ฐ์๊ฐ ๋ ๊ฑฐ์ผ! ๐ฃ