[๐ค] Next.js App Router ์บ์ฑ ์ ๋ต: ๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ (revalidatePath, revalidateTag) ์๋ฒฝ ๊ฐ์ด๋
Next.js 14 App Router์์ ํจ์จ์ ์ธ ๋ฐ์ดํฐ ์บ์ฑ ์ ๋ต๊ณผ revalidatePath, revalidateTag๋ฅผ ์ด์ฉํ ๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ ๋ฐฉ๋ฒ์ ์ค๋ฌด ์์์ ํจ๊ป ์์ธํ ์์๋ณด๊ณ ์น ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
์ ๋ณด๐ค ์ด ํฌ์คํ ์ Gemini 2.5 Flash AI๊ฐ ์์ฑํ์ด์. ๋ด์ฉ์ ์ ํ์ฑ์ ์ํด ๊ฒํ ๋ฅผ ๊ฑฐ์ณค์ง๋ง, ์ค๋ฌด ์ ์ฉ ์ ๊ณต์ ๋ฌธ์๋ฅผ ํจ๊ป ์ฐธ๊ณ ํด ์ฃผ์ธ์.
์ ์ฉํ ํNext.js 14 App Router์์ ๋ฐ์ดํฐ ์บ์ฑ์ ํต์ฌ ์๋ฆฌ๋ฅผ ์ดํดํ๊ณ , revalidatePath, revalidateTag๋ฅผ ํ์ฉํ ํจ๊ณผ์ ์ธ ๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ ์ ๋ต์ ์ค๋ฌด ์์์ ํจ๊ป ์์ธํ ๋ค๋ฃจ๊ณ ์์ด์.
๐ค ๋ฌธ์ /๋ฐฐ๊ฒฝ
0๏ธโฃ ์ ์ด ์ฃผ์ ๋ฅผ ๋ค๋ฃจ๋๊ฐ
Next.js 13๋ถํฐ ๋์
๋ App Router๋ ๊ธฐ์กด Pages Router์๋ ์ ํ ๋ค๋ฅธ ๋ฐ์ดํฐ ํ์นญ ๋ฐ ์บ์ฑ ์ ๋ต์ ์ ๊ณตํด์.
ํนํ Server Components์ ๋ฑ์ฅ๊ณผ ํจ๊ป ์บ์ฑ ๋ฉ์ปค๋์ฆ์ด ๋์ฑ ๋ณต์กํด์ ธ์ ๋ง์ ๊ฐ๋ฐ์๋ถ๋ค์ด ์ค๋ฌด์์ ํผ๋์ ๊ฒช๊ณ ๊ณ์ธ์.
์๋ก์ด ์บ์ฑ ์ ๋ต์ ์ ๋๋ก ์ดํดํ์ง ๋ชปํ๋ฉด, ์์์น ๋ชปํ stale data ๋ฌธ์ ๋ ๋นํจ์จ์ ์ธ ๋คํธ์ํฌ ์์ฒญ์ผ๋ก ์ธํด ์ฑ๋ฅ ์ ํ๊ฐ ๋ฐ์ํ ์ ์๋ต๋๋ค.
์ด ๊ธ์์๋ App Router์ ์บ์ฑ ์๋ฆฌ๋ฅผ ๋ช
ํํ ์ดํดํ๊ณ , ๋ฐ์ดํฐ๋ฅผ ์ต์ ์ํ๋ก ์ ์งํ๊ธฐ ์ํ ํ์์ ์ธ ์ฌ๊ฒ์ฆ(Revalidation) ๋ฐฉ๋ฒ์ ๋ํด ์์ธํ ์์๋ณด๋ ค๊ณ ํด์.
1๏ธโฃ ๊ธฐ์กด ๋ฐฉ์์ ํ๊ณ
Pages Router์์๋ getServerSideProps, getStaticProps, getStaticPaths ๋ฑ์ ํตํด ๋ฐ์ดํฐ ํ์นญ๊ณผ ์บ์ฑ์ ์ ์ดํ์ด์.
getStaticProps์ revalidate ์ต์
์ ์ฌ์ฉํ๊ฑฐ๋ res.revalidate๋ฅผ ํธ์ถํ๋ ๋ฐฉ์์ผ๋ก ISR(Incremental Static Regeneration)์ ๊ตฌํํ์ฃ .
ํ์ง๋ง App Router์์๋ ์ด๋ฌํ ๋ฐฉ์์ด ์ฌ๋ผ์ง๊ณ , React์ fetch API ํ์ฅ๊ณผ ์๋ฒ ์ก์
(Server Actions)์ ํตํด ์บ์ฑ๊ณผ ์ฌ๊ฒ์ฆ์ ๊ด๋ฆฌํด์.
์ด ๋ณํ๋ ๊ฐ๋ฐ์๋ค์๊ฒ ๋ ํฐ ์ ์ฐ์ฑ์ ์ ๊ณตํ์ง๋ง, ๋์์ ์ด๋์ ์ด๋ป๊ฒ ์บ์ฑ์ด ๋ฐ์ํ๋์ง, ๊ทธ๋ฆฌ๊ณ ์ธ์ ๋ฐ์ดํฐ๋ฅผ ์ฌ๊ฒ์ฆํด์ผ ํ๋์ง์ ๋ํ ์๋ก์ด ํ์ต์ด ํ์ํ๊ฒ ๋ง๋ค์์ด์.
โ๏ธ ํด๊ฒฐ ๋ฐฉ๋ฒ
0๏ธโฃ ํต์ฌ ์์ด๋์ด
Next.js App Router์ ์บ์ฑ ์ ๋ต์ ํฌ๊ฒ 3๊ฐ์ง ๋ ์ด์ด์์ ์๋ํด์.
- Request Memoization: ๋์ผํ
fetchํธ์ถ์ด ํ ๋ฒ์ ๋ ๋๋ง ์ฃผ๊ธฐ ๋ด์์ ์ค๋ณต ์คํ๋๋ ๊ฒ์ ๋ฐฉ์งํด์. - Data Cache (Full Route Cache):
fetch์์ฒญ์ ๊ฒฐ๊ณผ๋ฅผ ์๋ฒ์ ์ ์ฅํด์ ๋์ผํ ์์ฒญ์ ๋ํด ์ฌ์ฌ์ฉํด์. ์ด๋ SSG/ISR๊ณผ ์ ์ฌํ ๋ฐฉ์์ผ๋ก ๋์ํ๋ฉฐ,revalidate์ต์ ์ผ๋ก ์ ์ดํ ์ ์์ด์. - Router Cache: ํด๋ผ์ด์ธํธ ์ธก์์ Soft Navigation ์ ์ด์ ์ ๋ฐฉ๋ฌธํ๋ ๋ผ์ฐํธ์ React Server Components ํ์ด๋ก๋(payload)๋ฅผ ์บ์ฑํด์.
์ด ์ค์์ ์ฐ๋ฆฌ๊ฐ ์ค์ ์ ์ผ๋ก ๋ค๋ฃฐ ๋ถ๋ถ์ Data Cache์ ์ด๋ฅผ ๋ฌดํจํํ๋ **๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ(Revalidation)**์ด์์.
Next.js๋ fetch ์์ฒญ์ ๋ํด ๊ธฐ๋ณธ์ ์ผ๋ก force-cache ์ ๋ต์ ์ฌ์ฉํ๋ฉฐ, revalidate ์ต์
์ ํตํด ์บ์ฑ ์๊ฐ์ ์ ์ดํด์.
ํ์ง๋ง ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋์์ ๋ ์ฆ์ ์บ์๋ฅผ ๋ฌดํจํํ๊ณ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์์ผ ํ ๋๊ฐ ์์ฃ .
์ด๋ revalidatePath์ revalidateTag ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ํน์ ๊ฒฝ๋ก(path) ๋๋ ํ๊ทธ(tag)์ ์ฐ๊ด๋ ์บ์๋ฅผ ์๋์ผ๋ก ๋ฌดํจํํ ์ ์์ด์.
1๏ธโฃ ์ ์ฉ ๋ฐฉ๋ฒ
1. fetch ์บ์ฑ ์ต์
ํ์ฉ
Next.js๋ fetch API๋ฅผ ํ์ฅํ์ฌ ์บ์ฑ ๋์์ ์ ์ดํ ์ ์๋๋ก ํ์ด์.
๊ธฐ๋ณธ์ ์ผ๋ก fetch๋ ์บ์๋ฅผ ์ฌ์ฉํ๋ฉฐ, ํน์ ์๊ฐ(์ด ๋จ์) ์ดํ์ ์ฌ๊ฒ์ฆํ๋๋ก ์ค์ ํ ์ ์์ด์.
async function getPosts() { const res = await fetch('https://api.example.com/posts', { next: { revalidate: 3600 } // 1์๊ฐ๋ง๋ค ๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ (ISR๊ณผ ์ ์ฌ) }); if (!res.ok) throw new Error('Failed to fetch posts'); return res.json(); } // ์บ์ ์ฌ์ฉ ์ ํจ (SSR๊ณผ ์ ์ฌ) async function getDynamicData() { const res = await fetch('https://api.example.com/dynamic', { cache: 'no-store' // ์ด ์์ฒญ์ ์บ์ํ์ง ์์ }); if (!res.ok) throw new Error('Failed to fetch dynamic data'); return res.json(); }
next: { revalidate: N } ์ต์
์ N์ด๋ง๋ค ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋ฐ์ดํฐ๋ฅผ ์ฌ๊ฒ์ฆํ์ฌ stale-while-revalidate ๋ฐฉ์์ผ๋ก ๋์ํ๊ฒ ํด์.
cache: 'no-store'๋ ์บ์๋ฅผ ์ ํ ์ฌ์ฉํ์ง ์๊ณ ๋งค๋ฒ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋๋ก ๊ฐ์ ํด์.
2. revalidatePath๋ก ๊ฒฝ๋ก ๊ธฐ๋ฐ ์ฌ๊ฒ์ฆ
revalidatePath ํจ์๋ ํน์ ๊ฒฝ๋ก์ ๊ด๋ จ๋ ๋ชจ๋ ์บ์๋ฅผ ๋ฌดํจํํด์.
์ฃผ๋ก ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ํ ํด๋น ๋ฐ์ดํฐ๊ฐ ํ์๋๋ ํ์ด์ง์ ์บ์๋ฅผ ์
๋ฐ์ดํธํ ๋ ์ฌ์ฉํด์.
'use server'; // Server Action์์ ์ฌ์ฉ import { revalidatePath } from 'next/cache'; export async function addPost(formData: FormData) { const title = formData.get('title'); const content = formData.get('content'); // ... ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ฒ์๋ฌผ ์ถ๊ฐ ๋ก์ง ... await new Promise(resolve => setTimeout(resolve, 500)); // DB ์์ ์๋ฎฌ๋ ์ด์ console.log('๊ฒ์๋ฌผ ์ถ๊ฐ๋จ:', { title, content }); // ๊ฒ์๋ฌผ ๋ชฉ๋ก ํ์ด์ง์ ์บ์๋ฅผ ๋ฌดํจํํ์ฌ ์ต์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋๋ก ํจ revalidatePath('/posts'); }
์ ์์์์๋ ์๋ก์ด ๊ฒ์๋ฌผ์ ์ถ๊ฐํ ํ /posts ๊ฒฝ๋ก์ ์บ์๋ฅผ ๋ฌดํจํํ์ฌ, ์ฌ์ฉ์๊ฐ /posts ํ์ด์ง์ ์ ๊ทผํ ๋ ์ต์ ๊ฒ์๋ฌผ ๋ชฉ๋ก์ ๋ณผ ์ ์๋๋ก ํด์.
revalidatePath๋ Server Action์ด๋ Route Handler ๋ด๋ถ์์ ํธ์ถ๋์ด์ผ ํด์.
3. revalidateTag๋ก ํ๊ทธ ๊ธฐ๋ฐ ์ฌ๊ฒ์ฆ
revalidateTag ํจ์๋ fetch ์์ฒญ์ next: { tags: ['tag-name'] } ์ต์
์ผ๋ก ์ค์ ๋ ํน์ ํ๊ทธ์ ๊ด๋ จ๋ ๋ชจ๋ ์บ์๋ฅผ ๋ฌดํจํํด์.
์ด๋ ์ฌ๋ฌ ๊ฒฝ๋ก์์ ๋์ผํ ๋ฐ์ดํฐ(์: ํน์ ์ฌ์ฉ์ ์ ๋ณด, ์ํ ์นดํ
๊ณ ๋ฆฌ)๋ฅผ ๊ณต์ ํ ๋ ์ ์ฉํด์.
// app/posts/[id]/page.tsx ๋๋ app/components/PostList.tsx async function getPost(id: string) { const res = await fetch(`https://api.example.com/posts/${id}`, { next: { tags: ['posts'] } // 'posts' ํ๊ทธ๋ฅผ ์ด ์์ฒญ์ ํ ๋น }); if (!res.ok) throw new Error('Failed to fetch post'); return res.json(); } // app/users/[id]/page.tsx async function getUserPosts(userId: string) { const res = await fetch(`https://api.example.com/users/${userId}/posts`, { next: { tags: ['userPosts', `user-${userId}-posts`] } // ์ฌ๋ฌ ํ๊ทธ ํ ๋น ๊ฐ๋ฅ }); if (!res.ok) throw new Error('Failed to fetch user posts'); return res.json(); } // app/actions.ts (Server Action) 'use server'; import { revalidateTag } from 'next/cache'; export async function updatePost(postId: string, newContent: string) { // ... ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ํน์ ๊ฒ์๋ฌผ ์ ๋ฐ์ดํธ ๋ก์ง ... await new Promise(resolve => setTimeout(resolve, 500)); // DB ์์ ์๋ฎฌ๋ ์ด์ console.log('๊ฒ์๋ฌผ ์ ๋ฐ์ดํธ๋จ:', { postId, newContent }); // 'posts' ํ๊ทธ๊ฐ ์๋ ๋ชจ๋ fetch ์์ฒญ์ ์บ์๋ฅผ ๋ฌดํจํ revalidateTag('posts'); // ๋ง์ฝ ํน์ ์ฌ์ฉ์ ๊ฒ์๋ฌผ๋ง ์ ๋ฐ์ดํธ๋์๋ค๋ฉด ํด๋น ์ฌ์ฉ์ ํ๊ทธ๋ง ๋ฌดํจํ // revalidateTag(`user-${userId}-posts`); }
revalidateTag๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํน์ ํ
์ด๋ธ์ด๋ ์ปฌ๋ ์
๊ณผ ๊ฐ์ด ๋
ผ๋ฆฌ์ ์ผ๋ก ๋ฌถ์ธ ๋ฐ์ดํฐ ๊ทธ๋ฃน์ ์บ์๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐ ๊ฐ๋ ฅํ ๋๊ตฌ์์.
๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋์์ ๋, ๊ด๋ จ ํ๊ทธ๋ง ๋ฌดํจํํ๋ฉด ํด๋น ํ๊ทธ๋ฅผ ์ฌ์ฉํ๋ ๋ชจ๋ ํ์ด์ง๊ฐ ๋ค์ ์์ฒญ ์ ์ต์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ฒ ๋๋ต๋๋ค.
๐งช ์์
0๏ธโฃ ์ฝ๋/์ค์ ์์
๊ฒ์๋ฌผ ๋ชฉ๋ก๊ณผ ์์ธ ํ์ด์ง๊ฐ ์๊ณ , ์๋ก์ด ๊ฒ์๋ฌผ์ ์ถ๊ฐํ๋ ๊ธฐ๋ฅ์ ๊ฐ์ ํด๋ณผ๊ฒ์.
1. ๊ฒ์๋ฌผ ๋ชฉ๋ก ํ์ด์ง (app/posts/page.tsx)
import Link from 'next/link'; async function getPosts() { const res = await fetch('https://api.example.com/posts', { next: { tags: ['posts'], revalidate: 60 } // 'posts' ํ๊ทธ๋ฅผ ํ ๋นํ๊ณ 60์ด๋ง๋ค ์ฌ๊ฒ์ฆ }); if (!res.ok) { throw new Error('๊ฒ์๋ฌผ์ ๋ถ๋ฌ์ค์ง ๋ชปํ์ด์.'); } return res.json(); } export default async function PostsPage() { const posts = await getPosts(); return ( <div className="container mx-auto p-4"> <h1 className="text-2xl font-bold mb-4">๊ฒ์๋ฌผ ๋ชฉ๋ก</h1> <Link href="/posts/new" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mb-4 inline-block"> ์ ๊ฒ์๋ฌผ ์์ฑ </Link> <ul className="space-y-2"> {posts.map((post: any) => ( <li key={post.id} className="border p-3 rounded shadow-sm"> <Link href={`/posts/${post.id}`} className="text-lg font-semibold text-blue-600 hover:underline"> {post.title} </Link> <p className="text-gray-600">{post.content.substring(0, 100)}...</p> </li> ))} </ul> </div> ); }
2. ์๋ก์ด ๊ฒ์๋ฌผ ์์ฑ ํ์ด์ง (app/posts/new/page.tsx)
import { redirect } from 'next/navigation'; import { revalidatePath, revalidateTag } from 'next/cache'; // ํ์ํ ํจ์ ์ํฌํธ // Server Action ์ ์ async function createPost(formData: FormData) { 'use server'; const title = formData.get('title') as string; const content = formData.get('content') as string; if (!title || !content) { console.error('์ ๋ชฉ๊ณผ ๋ด์ฉ์ ๋ชจ๋ ์ ๋ ฅํด์ผ ํด์.'); return; } // ์ค์ API ํธ์ถ ๋๋ DB ์ ์ฅ ๋ก์ง console.log('์ ๊ฒ์๋ฌผ ์ ์ฅ ์ค:', { title, content }); await new Promise(resolve => setTimeout(resolve, 1000)); // ๋น๋๊ธฐ ์์ ์๋ฎฌ๋ ์ด์ // ์ ์ฅ ํ ์บ์ ๋ฌดํจํ revalidateTag('posts'); // 'posts' ํ๊ทธ๋ฅผ ๊ฐ์ง ๋ชจ๋ fetch ์บ์ ๋ฌดํจํ revalidatePath('/posts'); // '/posts' ๊ฒฝ๋ก์ ์บ์ ๋ฌดํจํ (Router Cache ํฌํจ) redirect('/posts'); // ๊ฒ์๋ฌผ ๋ชฉ๋ก ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ } export default function NewPostPage() { return ( <div className="container mx-auto p-4"> <h1 className="text-2xl font-bold mb-4">์ ๊ฒ์๋ฌผ ์์ฑ</h1> <form action={createPost} className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"> <div className="mb-4"> <label htmlFor="title" className="block text-gray-700 text-sm font-bold mb-2">์ ๋ชฉ</label> <input type="text" id="title" name="title" className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" required /> </div> <div className="mb-6"> <label htmlFor="content" className="block text-gray-700 text-sm font-bold mb-2">๋ด์ฉ</label> <textarea id="content" name="content" rows={5} className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" required></textarea> </div> <div className="flex items-center justify-between"> <button type="submit" className="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"> ๊ฒ์๋ฌผ ์ ์ฅ </button> </div> </form> </div> ); }
1๏ธโฃ ์ ์ฉ ๊ฒฐ๊ณผ
์ ์์ ์ฝ๋์์ createPost Server Action์ด ์คํ๋๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ผ๋ค์ด ๋ฐ์ํด์.
- ๋ฐ์ดํฐ ์ ์ฅ: ์๋ก์ด ๊ฒ์๋ฌผ์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฑ๊ณต์ ์ผ๋ก ์ ์ฅ๋ผ์.
revalidateTag('posts'):getPostsํจ์์์next: { tags: ['posts'] }๋ก ์ค์ ๋ ๋ชจ๋fetch์์ฒญ์ Data Cache๊ฐ ๋ฌดํจํ๋ผ์.
์ด๋/postsํ์ด์ง๋ฟ๋ง ์๋๋ผ, ๋ค๋ฅธ ํ์ด์ง์์postsํ๊ทธ๋ฅผ ์ฌ์ฉํ์ฌ ๊ฒ์๋ฌผ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋ชจ๋ ๊ณณ์์ ์บ์๊ฐ ๋ฌดํจํ๋จ์ ์๋ฏธํด์.revalidatePath('/posts'):/posts๊ฒฝ๋ก์ ๊ด๋ จ๋ Router Cache ๋ฐ Data Cache๊ฐ ๋ฌดํจํ๋ผ์.
์ฌ์ฉ์๊ฐ/postsํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ๋๊ฑฐ๋ ๋ค์ ๋ฐฉ๋ฌธํ ๋, Next.js๋ ์ต์ ๋ฐ์ดํฐ๋ฅผ ๋ค์ ํ์นญํ๊ฒ ๋ผ์.
revalidatePath๋ ์ผ๋ฐ์ ์ผ๋กrevalidateTag์ ํจ๊ป ์ฌ์ฉ๋ ๋ ๋ ํจ๊ณผ์ ์ด์์.
revalidateTag๋ ๋ฐ์ดํฐ ์บ์๋ฅผ ๋ฌดํจํํ๊ณ ,revalidatePath๋ ํด๋ผ์ด์ธํธ ์ธก์ Router Cache๋ฅผ ๋ฌดํจํํ์ฌ ์ฆ๊ฐ์ ์ธ UI ์ ๋ฐ์ดํธ๋ฅผ ๋ณด์ฅํ๋ ๋ฐ ๋์์ ์ค์.
์ด๋ฌํ ๊ณผ์ ์ ํตํด ์ฌ์ฉ์๋ ์๋ก์ด ๊ฒ์๋ฌผ์ด ์ถ๊ฐ๋ ์ต์ ๊ฒ์๋ฌผ ๋ชฉ๋ก์ ์ฆ์ ํ์ธํ ์ ์๊ฒ ๋๋ ๊ฒ์ด์ฃ .
๐ ์ ๋ฆฌ
0๏ธโฃ ํต์ฌ ์์ฝ
Next.js App Router์ ์บ์ฑ ์ ๋ต์ ์น ์ ํ๋ฆฌ์ผ์ด์
์ ์ฑ๋ฅ์ ํฌ๊ฒ ํฅ์์ํฌ ์ ์๋ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ด์์.
fetch API์ next: { revalidate: N } ์ต์
์ ํตํด ISR๊ณผ ์ ์ฌํ๊ฒ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ์ฌ๊ฒ์ฆํ ์ ์๊ณ , cache: 'no-store'๋ก ์บ์ฑ์ ๋นํ์ฑํํ ์๋ ์์ด์.
๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋ ์ฆ์ ์บ์๋ฅผ ๋ฌดํจํํ์ฌ ์ต์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ํ๋ ค๋ฉด, revalidatePath์ revalidateTag๋ฅผ ํ์ฉํด์ผ ํด์.
revalidatePath(path): ํน์ ๊ฒฝ๋ก์ ๊ด๋ จ๋ ์บ์๋ฅผ ๋ฌดํจํํ์ฌ, ํด๋น ๊ฒฝ๋ก๋ก ์ ๊ทผ ์ ์ต์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋๋ก ํด์.revalidateTag(tag):fetch์์ฒญ์ ์ค์ ๋ ํน์ ํ๊ทธ์ ๊ด๋ จ๋ ๋ชจ๋ ์บ์๋ฅผ ๋ฌดํจํํ์ฌ, ๋ ผ๋ฆฌ์ ์ผ๋ก ๋ฌถ์ธ ๋ฐ์ดํฐ ๊ทธ๋ฃน์ ์ผ๊ด์ฑ์ ์ ์งํ ์ ์๋๋ก ๋์์ค์.
์ด ๋ ํจ์๋ฅผ Server Action์ด๋ Route Handler ๋ด์์ ์ ์ ํ ์ฌ์ฉํ์ฌ, ์ฌ์ฉ์์๊ฒ ํญ์ ์ต์ ์ ์ ํํ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๋ฉด์๋ ๋ฐ์ด๋ ์ฑ๋ฅ์ ์ ์งํ ์ ์๋ต๋๋ค.
1๏ธโฃ ๋ค์ ์ก์
- ๊ณต์ ๋ฌธ์ ์ฌํ ํ์ต: Next.js ๊ณต์ ๋ฌธ์์ Caching ์น์
์ ๋ค์ ํ๋ฒ ๊ผผ๊ผผํ ์ฝ์ด๋ณด๋ฉฐ ๊ฐ ์บ์ฑ ๋ ์ด์ด์ ๋์ ๋ฐฉ์์ ๋ ๊น์ด ์ดํดํด ๋ณด์ธ์.
- ์ค์ ํ๋ก์ ํธ ์ ์ฉ: ํ์ฌ ์งํ ์ค์ธ Next.js App Router ํ๋ก์ ํธ์
revalidatePath์revalidateTag๋ฅผ ์ ์ฉํ์ฌ ๋ฐ์ดํฐ ๋ณ๊ฒฝ ํ ์บ์ ๋ฌดํจํ๊ฐ ์ด๋ป๊ฒ ์ด๋ฃจ์ด์ง๋์ง ์ง์ ๊ฒฝํํด ๋ณด์ธ์. - ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง: ์บ์ฑ ์ ๋ต ์ ์ฉ ํ ์น ์ฑ๋ฅ ์งํ(Core Web Vitals ๋ฑ)๋ฅผ ๋ชจ๋ํฐ๋งํ์ฌ ์ค์ ์ฑ๋ฅ ๊ฐ์ ํจ๊ณผ๋ฅผ ์ธก์ ํด ๋ณด๋ ๊ฒ์ด ์ค์ํด์.
์ด ๊ธ์ด Next.js App Router์ ์บ์ฑ๊ณผ ์ฌ๊ฒ์ฆ ์ ๋ต์ ์ดํดํ๊ณ ์ค๋ฌด์ ์ ์ฉํ๋ ๋ฐ ํฐ ๋์์ด ๋๊ธฐ๋ฅผ ๋ฐ๋ผ์.
๐ฎ ์ฐธ๊ณ
์ฑ๊ณต์ฐ๊ด๋ ํฌ์คํธ๊ฐ ์์ด์ ๋๋คํ ํฌ์คํธ๋ก ๋์ฒดํฉ๋๋ค.