๐ Next.js ์ฌํ 1์ฅ: Caching Deep Dive โ 4์ค ์บ์ ์ํคํ ์ฒ ํํค์น๊ธฐ
๐ ๊ฐ์
4์ค ์บ์ ์ํคํ ์ฒ(Request Memoization, Data Cache, Full Route Cache, Router Cache)๋ฅผ ์์ ํ ์ดํดํฉ๋๋ค.
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค ์ ์์์ผ ํ๋๊ฐ
- ๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
- ๐งฉ 1๋จ๊ณ: Request Memoization (์๋ช 1์ด์ ํจ์ ์บ์) ๐ข
- ๐ฑ 2๋จ๊ณ: Data Cache (DB ์ฟผ๋ฆฌ์ ์๊ตฌ ๋ณด์กด) ๐ก
- ๐ก๏ธ 3๋จ๊ณ: Full Route Cache (์์ฑ๋ HTML์ ํ์ํ) ๐ด
- ๐ฅ 4๋จ๊ณ: Router Cache (ํด๋ผ์ด์ธํธ ํญ ๋ฉ๋ชจ๋ฆฌ) ๐ฃ
- ๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
- ๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
- ๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
- ๐ ๋ ์์๋ณด๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 20๋ถ (์ ์ฒด) / ํต์ฌ ํํธ๋ง: 10๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ์์ฒ (์ ์ ): "์ด? ๊ด๋ฆฌ์ ์ด๋๋ฏผ ํ์ด์ง์์ ๊ณต์ง์ฌํญ ์ ๋ชฉ์ ๋ผ ๋น ์ง๊ฒ '๊ธด๊ธ ํจ์น 2.0'์ผ๋ก ์์ ํ๊ณ DB ์ ์ฅ(UPDATE)๊น์ง ์๋ฒฝํ๊ฒ ์ฑ๊ณต์์ผฐ์ด์! ๊ทธ๋ฐ๋ฐ ๋ฉ์ธ ํ๋ฉด์ผ๋ก ๋์๊ฐ์ ์๋ก๊ณ ์นจ(F5)์ ์๋ฌด๋ฆฌ ๋๋ฌ๋ ์์ ์ ๋ชฉ์ธ '๊ธด๊ธ ํจ์น 1.0'๋ง ๊ณ์ ๋ ์! ๋ฌด๋ ค 30๋ถ์ด๋ ์ง๋ ์ง๊ธ๋์. ํ์ฌ ์ปดํจํฐ๊ฐ ๋ฏธ์น ๊ฑด๊ฐ์?!"
- ์ํธ(๋ฆฌ๋): "์์ฒ ๋... Next.js์ ์ ๋ง ๊ฐ์ 'Data Cache'์ 'Full Route Cache'๋ผ๋ ์ด์ค ์๋ฌผ์ ๋ฅผ ์ ํ์์์์! Next.js ์๋ฒ๋ ํ ๋ฒ ๊ธ์ด์จ ์ ์ (Static) ๋ฐ์ดํฐ๋ฅผ ์ต์ง๋ก ๋๋ ค ๋ถ์๊ธฐ ์ ๊น์ง๋ ์ง์ ค์ฒ๋ผ ์์ํ ๋ณด์กดํ ์ดํ๋ฅผ ํ์ด์ค๋ค๊ตฌ์!"
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
Next.js ์บ์์ ์
๋ช
โ 4๊ฐ์ง ์บ์ ๊ณ์ธต ์๋ฒฝ ๋ถํด โ ๊ฐ ์บ์์ ์ด๊ธฐํ(Invalidate) ์๋ฆฌ
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
- ๋ด ์ฝ๋๊ฐ ์ด๋ ๊ณ์ธต์ ์บ์์ ๋ฌถ์ฌ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐฑ์ ํ์ง ๋ชปํ๊ณ ์๋์ง, "๋ฒ์ธ ๋ ์ด์ด"๋ฅผ ์ ํํ๊ฒ ์ง๋ชฉํ ์ ์๋ค.
- Vercel ๋น๋ ์ ํ๋ฉด ์์ ๋จ๋
โ (Static)๊ณผฦ (Dynamic)์ ์ง์ง ๋ด๋ถ ๋์ ์๋ฆฌ๋ฅผ ์ค๋ช ํ ์ ์๋ค.
๐ค ์ ์์์ผ ํ๋๊ฐ
Next.js 13+ ๋ฒ์ (App Router)์ด ์ฒ์ ๋ฑ์ฅํ์ ๋ ์ ์ธ๊ณ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ค์ด "์บ์ ๊ดด๋ฌผ"์ด๋ผ๊ณ ์์ ํผ๋ถ์์ด.
์ด์ ๋ฒ์ ์ด "ํ์ํ ๋ ์์์ ์บ์ฑํด์ค" ์๋ค๋ฉด, App Router๋ "๊ธฐ๋ณธ๊ฐ์ด ๋ฌด์กฐ๊ฑด ์๊ตฌ ์บ์ฑ์ด์ผ! ์บ์ฑํ๊ธฐ ์ซ์ผ๋ฉด ๋๋ฅผ ์ค๋ํด๋ด(opt-out)!" ๋ผ๋ ๊ทน๋จ์ ์ธ ์บ์ ์ฐ์ ์ฃผ์(Cache by default)๋ก ๋ฐ๋์๊ฑฐ๋ .
์ ์ด๋ ๊ฒ๊น์ง ๋ ํ๊ฒ ๋ฐ๊ฟจ์๊น? ์๋ฒ ๋ ๋๋ง(SSR) ์์ฃผ์ ์น์ ๊ตฌ์กฐ์ ์ผ๋ก ๋งค์ฐ ๋น์ธ๊ณ ๋๋ฆฌ๊ธฐ ๋๋ฌธ์ด์ผ. ์ฌ์ฉ์๊ฐ 1๋ง ๋ช ๋ค์ด์ฌ ๋๋ง๋ค ์๋ฒ๊ฐ 1๋ง ๊ฐ์ HTML ๊ณต์ฅ์ ๋๋ฆฌ๊ณ DB๋ฅผ ์ฐ๋ฅด๋ฉด Vercel ์๊ธ์ด 100๋ฐฐ๋ก ํญ์ฃผํ ํ ๋๊น.
ํ์ง๋ง ์ด๊ฑธ ์๋ฒฝํ๊ฒ ์ดํดํ์ง ๋ชปํ๋ฉด, ์์ฒ ์ด์ฒ๋ผ ํ๋ฃจ ์ข
์ผ DB ๊ฐ์ด ์
๋ฐ์ดํธ ์ ๋๋ ์ง์ฅ์์ ํค๋งค๊ฒ ๋ผ.
Next.js๋ ๋จ์ผ ์บ์๊ฐ ์๋๋ผ 4๊ฐ์ ์บ์ ๊ณ์ธต(Layer)์ด ๋ง์น ์ํ ๊ป์ง์ฒ๋ผ ์๋ก๋ฅผ ๋๋ฌ์ธ๊ณ ์์ด. ์ด 4์ค ์ด๋์ ๋ฉํ ๋ชจ๋ธ์ ๋จธ๋ฆฟ์์ ๊ทธ๋ฆฌ๋ ๊ฒ์ด ์๋์ด์ ๊ฐ์ฅ ์๋ํ ์ฌ์ ์ ์์์ด์ผ.
๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
๐ง 5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด? (์์๋ค ํ๋ฒ๊ฑฐ ๊ณต์ฅ์ 4์ค ์คํผ๋ ์์คํ )
์๋์ด "๋น ๋งฅ ํ๋!" ์ฃผ๋ฌธํ์ ๋ ๊ณต์ฅ ๋ด๋ถ์์ ์ผ์ด๋๋ 4๋จ๊ณ ์ต์ ํ:
- 1๋จ๊ณ (Router Cache - ๋ด ์ค๋งํธํฐ): "์ด? ๋ ๋ฐฉ๊ธ ๋น ๋งฅ ์์ผฐ์๋๋ฐ. ๋ฐฐ๋ฌ ์ ์ํค๊ณ ๋ด ์ฃผ๋จธ๋์์ ์๊น ์ํจ ๋น ๋งฅ(์ฌ์ง) ๊บผ๋ด์ ๋ด์ผ์ง~" (์๋ก๊ณ ์นจ ์ ๊น์ง ์ ์ง)
- 2๋จ๊ณ (Request Memoization - ํ ๋ฒ ๋ฐฐ๋ฌํ ๋): "์ด๋ฒ์ ์ง์ง ๋ฐฐ๋ฌ ์์ผฐ์ด. ์ฃผ๋ฐฉ์ฅ์ด ๊ณ ๊ธฐ ๊ตฝ๋๋ฐ, ๋ด๊ฐ ๊ณ ๊ธฐ(fetch) 2๋ฒ ๊ฐ๋ค ๋ฌ๋ผ๊ณ ํด๋ ์ฃผ๋ฐฉ์ฅ์ด ๋ฌด์ํ๊ณ ํ ๋ฒ ๊ตฌ์ด ๊ณ ๊ธฐ๋ฅผ ๋ฐ๋ฐ ๋๋ ์ค!"
- 3๋จ๊ณ (Data Cache - ์ฃผ๋ฐฉ์ ์๊ตฌ ๋๋๊ณ ): "์ด์ ๋ค์ด์จ ํน์ ์์ค(DB ๊ฒฐ๊ณผ)๋ฅผ ์๊ตฌ ๋๋๊ณ ์ ๋ณด๊ดํด๋์ด. ๋ด์ผ ์๋์ด ์๋, ๋ชจ๋ ์๋ ์ ๋ฒ๋ฆฌ๊ณ ๋๊ฐ์ ์์ค๋ฅผ ๊ณ์ ๋ฟ๋ฆด ๊ฑฐ์ผ!"
- 4๋จ๊ณ (Full Route Cache - ์ง์ด์ฅ์ ๋ชจํ ํ๋ฒ๊ฑฐ): "์์ ์์ค, ๋นต, ์ผ์ฑ๋ฅผ ๋ค ์กฐ๋ฆฝํด๋ ์์ ํ ๋น ๋งฅ(HTML ํ์ผ ์์ฒด)์ ๊ฐ๊ฒ ์์ ์ ๋ฆฌ์ฅ์์ผ๋ก ๊ฑธ์ด๋์ด! ์ฃผ๋ฌธ ์ค๋ฉด ์ฃผ๋ฐฉ์ฅ ์ ๊นจ์ฐ๊ณ ์ ๋ชจํ ๋ฐ๋ก ๋์ ธ์ค ใ ใ ใ "
๐งฉ 1๋จ๊ณ: Request Memoization (์๋ช 1์ด์ ํจ์ ์บ์) ๐ข
์ด๊ฑด ์ด๋ฏธ guide 6์ฅ์์ ๋ง์คํฐํ ๋ด์ฉ์ด์ผ. ๊ฐ๋ณ๊ฒ ์ง๊ณ ๋์ด๊ฐ์.
ํน์ง ์์ฝ
- ๋ชฉ์ : ๊น๊ณ ๋ณต์กํ ์ปดํฌ๋ํธ ํธ๋ฆฌ ํ์์์ ๋ฐ์ดํฐ๋ฅผ ๋๊ฐ์ด ๋ ํธ์ถํ ๋, ๋คํฌ์ํฌ ๋ถํ ๋ฐฉ์ง (Props Drilling ํํผ)
- ๋์: ์ค๋ก์ง
GET๋ฐฉ์์fetch()ํจ์. (๊ทธ๋ฆฌ๊ณ ์๋์ผ๋ก ๋ํํReact.cache) - ์๋ช : ๋ธ๋ผ์ฐ์ ๊ฐ ํ๋ฉด์ ๊ฐฑ์ ํ๋ ๋ฑ ๊ทธ "ํ ๋ฒ์ ๋ ๋๋ง ํด(Lifecycle)" ๋์๋ง ์ด์์์.
- ์ด๊ธฐํ(Revalidation) ๋ฐฉ๋ฒ: ์์. ์ปดํฌ๋ํธ ๋ ๋๋ง์ด ๋ฑ ๋๋๋ 1์ด ๋ค์ ๋ฉ๋ชจ๋ฆฌ์์ ์ฐ๊ธฐ์ฒ๋ผ ์ฌ๋ผ์ง.
๐ฑ 2๋จ๊ณ: Data Cache (DB ์ฟผ๋ฆฌ์ ์๊ตฌ ๋ณด์กด) ๐ก
์ฌ๊ธฐ์๋ถํฐ๊ฐ ์ง์ง ๊ณจ์น ์ํ ๋๋ค์ ์์์ด์ผ. ์์ฒ ์ด๊ฐ 30๋ถ ๋์ ์ฃฝ์ด๋ผ ๊ณ ์ํ ๋ฒ์ธ์ด์ง.
ํน์ง
- ๋ชฉ์ : ๊ฐ์ URL๊ณผ ํ๋ผ๋ฏธํฐ๋ก ๋ค์ด์ค๋ ์ธ๋ถ DB๋ API ํต์ ๊ฒฐ๊ณผ๋ฅผ ๋์คํฌ(์๋ฒ) ์ด๋๊ฐ์ ๊ฑฐ๋ฏธ์ค์ฒ๋ผ ์์ํ ๊ฐ๋ฌ๋ฒ๋ฆผ.
- ๋์:
fetch()์๋ต๊ฐ ๊ทธ ์์ฒด. - ์๋ช : ์๊ตฌ์ (Permanent). ๊ฐ๋ฐ์๊ฐ ์๋ฒ๋ฅผ ๋ด๋ ธ๋ค ์ผ๊ฑฐ๋ ๊ฐ์ ๋ก ์ง์ฐ๊ธฐ ์ ๊น์ง 10๋ ์ด ์ง๋๋ ๊ทธ ๋ฐ์ดํฐ๋ฅผ ๊ทธ๋๋ก ๋ฑ์!
โ ์์ฒ ์ด์ ํจ์ (๊ธฐ๋ณธ ์บ์ ์ง์ฅ)
// app/notice/page.tsx
export default async function NoticeInfo() {
// ๐ฃ ๋์ฐํ ํจ์ : Next 13, 14 ์์๋ ์ด๊ฑธ ์ ๋ ์๊ฐ "ํ์ ์บ์" ๋ก ๋์ํจ!
// (Next 15๋ถํฐ๋ ๋คํํ no-store๋ก ์ ์ฑ
์ด ์ํ๋์์ง๋ง 14 ๋ฒ ์ด์ค ๋ ๊ฑฐ์๋ ์กฐ์ฌํด์ผ ํจ)
const res = await fetch('https://db.api/api/notice/urgent');
const data = await res.json();
return <div>{data.title}</div>
}โ ์ ์ด๊ถ ๋์ฐพ๊ธฐ (Opting out)
์์ฒ ๊ฐ์ง์ "์บ์ ์ ํด!" ํ์ถ ๋ฐฉ๋ฒ์ด ์์ด.
1) ๋ถ๋ถ ํ์ถ (Fetch ๋ ๋ฒจ)
fetch(url, { cache: 'no-store' }) // Next.js์ผ, ์ ๋ฐ ์ด ์ฟผ๋ฆฌ๋งํผ์ ๋งค๋ฒ ์ค์๊ฐ์ผ๋ก ๊ฐ์ ธ์ค๋ ด!
2) ํ์ผ ์ ์ฒด ํ์ถ (Segment Configuration)
export const dynamic = 'force-dynamic' // ์ด ํ์ผ(page) ๋ด์ ๋ชจ๋ ๋ก์ง ์บ์ ๊ฑฐ๋ถ!
export default async function Page() { ... }3) ์๊ฐ์ ๋ณด๊ด (ISR - Time based Revalidation)
fetch(url, { next: { revalidate: 3600 } }) // Next.js์ผ, ์ด๊ฑฐ 1์๊ฐ(3600์ด) ์นด์ดํธ๋ค์ดํ๊ณ ์บ์ ํ๊ธฐํด.
๐ก๏ธ 3๋จ๊ณ: Full Route Cache (์์ฑ๋ HTML์ ํ์ํ) ๐ด
Data Cache๊ฐ "DB ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ(JSON)"๋ฅผ ์กฐ๊ฐ์กฐ๊ฐ ์ผ๋ ค๋๋ ๊ฑฐ๋ผ๋ฉด, Full Route Cache๋ ๋ ๊ทน๊ฐ์ ๋ณ๊ธฐ์ผ.
์์ ํ๋ฉด ์ ์ฒด HTML(JS๋ฒ๋ค ํฌํจ) ๋ฉ์ด๋ฆฌ๋ฅผ ํต์งธ๋ก ๋น๋ ํ์์ ๊ตฌ์์ ํ์์ผ๋ก ๋ง๋ค์ด๋ฒ๋ ค!
ํน์ง
- ๋ชฉ์ : ์๋ฒ ๊นกํต CPU๋ฅผ ์๋ผ๊ธฐ ์ํด, 1์ด์ ์ง์ฐ๋ ์์ด ๋ฒ๊ฐ์ฒ๋ผ ํ๋ฉด์ ์๋ตํ๊ธฐ ์ํ ์์ ํ ๋ณด๊ด.
- ์๋ช
: ์๊ตฌ์ . ๋ค์ ๋น๋(Build)๋ช
๋ น
npm run build๋ฅผ ์๋ก ์น๊ธฐ ์ ๊น์ง ์ฃฝ์ง ์์. - ์กฐ๊ฑด: 2๋จ๊ณ์ Data Cache๊ฐ ํ๋๋ผ๋ ์ด์์๋(Static) ์์ฑ์ ํ์ด์ง.
๐ Static vs Dynamic ๋ฃจํธ ํ๋ ๊ธฐ
ํฐ๋ฏธ๋์์ npm run build๋ฅผ ์ณค์ ๋ ๋จ๋ ์ฑ์ ํ๋ฅผ ์ ๋ด์ผ ํด.
Route (app) Size First Load JS
โ โ / 154 B 86.6 kB
โ โ /about 154 B 86.6 kB
โ ฦ /profile/[id] 154 B 86.6 kB
โ (Static) prerendered as static HTML (uses no initial data)
ฦ (Dynamic) server-rendered on demand using Node.jsโ (Static)ํ์ ์ ๋น๋ฐ: ๋ด ํ์ด์ง ์์no-store๊ฐ ๊ฑธ๋ฆฐ fetch๋ ์๊ณ , ๋ธ๋ผ์ฐ์ ์ค์๊ฐ ํค๋cookies(),headers(),searchParams(?id=1) ์ชผ๊ฐ๋ฆฌ๋ ๋จ ํ ๊ฐ๋ ์ฐ์ง ์์์ ๋ ํ๋! ์ด ์ถ๋ณต๋ฐ์ ํ์ผ์ Full Route Cache์ ํ์์ผ๋ก ์๊ตฌ ์ ์ฅ๋๋ฉฐ ๋น์ ์๋๋ก ์๋น๋๋ค.ฦ (Dynamic)ํ์ : ๋ก๊ทธ์ธ ์ธ์ฆ์ ์ํดcookies().get('token')์ ํ ์ค์ด๋ผ๋ ์ด ์๊ฐ, ์... ๋๊ตฐ์ง ๋งค๋ฒ ํ์ธํด์ผ ํ๋ค? ํ๋ฉด์ ํ์์ ๋ฐ์ด ๋ด๊ณ ๋งค๋ฒ SSR(์ฆ์ ๋ ๋๋ง)๋ก ๊ฐ๋ฑ๋นํ๋ค.
[์์ฒ ์ด ์๋ฌ์ ์ง์ ๊ท๋ช
]
์์ฒ ์ด์ ๊ณต์ง์ฌํญ ํ์ด์ง๋ ์๋ฒฝํ โ Static ํ์ผ์ด์์ด. DB ๊ฐ ์
๋ฐ์ดํธ ๋ก์ง(Admin)๋ง ๋ง๋ค์ด ๋จ์ง, ์ ์ ๊ฐ ๋ณด๋ ์ด ๋ผ์ฐํธ์ HTML ๋ฉ์ด๋ฆฌ ํ์(Full Route Cache)๊ณผ JSON(Data Cache)์ ๋ง์น๋ก ๊นจ๋ถ์ด์ ๋ค์ ๊ตฌ์ฐ๋ผ๋ ๋ช
๋ น์ด(revalidatePath)๋ฅผ ํธ์ถํ์ง ์์๋ ๊ฑฐ์ผ!
๐ฅ 4๋จ๊ณ: Router Cache (ํด๋ผ์ด์ธํธ ํญ ๋ฉ๋ชจ๋ฆฌ) ๐ฃ
์๋ฒ๋ฅผ ๊ด๋ฆฌํ๋ ์ 3๊ฐ์ ์บ์์ ๋ค๋ฅด๊ฒ, ๋ง์ง๋ง ๋ฐฉ์ด์ ์ ์ฌํ๋ฆฌ/ํฌ๋กฌ ๋ธ๋ผ์ฐ์ ์ ์ฌ์ฉ์ ์ปดํจํฐ์ ๋ฉ๋ชจ๋ฆฌ(RAM) ์์ ์ด์์์ด.
๋ธ๋ผ์ฐ์ ๋ ๋๋ํ๊ณ ๊ฒ์ผ๋ฅด๋ค
Next.js App Router๋ก ๋ค๋น๊ฒ์ด์ (Link ํ๊ทธ ํด๋ฆญ)์ ํ๋ฉด, ๊ธฐ์กด ๋ฆฌ์กํธ์ฒ๋ผ ์๋ฒ๋ก ์ ๊ฐ๊ณ ๋ธ๋ผ์ฐ์ ์์ ๊ฟ์ณ๋ ๋ ๋๋ง ์กฐ๊ฐ ๋ชจ์(RSC Payload)์ ๊บผ๋ด์จ.
- ์๋ช
: ๋ธ๋ผ์ฐ์ ํญ์ X๋ก ๋ซ๊ฑฐ๋
์๋ก๊ณ ์นจ(F5)์ ๋๋ฅด๊ธฐ ์ ๊น์ง(๋๋ 30์ด~5๋ถ ๋ด์ธ ๋ฉ๋ชจ๋ฆฌ ์ ์ฑ ๋ง๋ฃ ์ ๊น์ง). - ์ด ๋ ์ ๋๋ฌธ์ "๋ ๋ฐฉ๊ธ ๊ธ ์ผ๋๋ฐ ๋ค๋ก ๊ฐ๊ธฐ ๋๋ ๋๋ ์๋ ๋ชฉ๋ก ๋จ๋ค?" ๊ฐ์ ๋ฒ๊ทธ์ฑ ์ฒด๊ฐ์ด ๊ฐํ์ ์ผ๋ก ๋ฐ์ํด.
๐จ ๋ถ์๊ธฐ (Invalidation)
์ด ์บ์๊ฐ ๋ฐฉํด๊ฐ ๋ ๋๋ ๋ธ๋ผ์ฐ์ ์๊ฒ "๋ค์ํ์ผ๋ก ์ด ๋ฉ๋ชจ ์์ฒฉ ์ฐข์ด๋ฒ๋ ค!"๋ผ๊ณ ๋ช ๋ นํด์ผ ํด.
'use client'
import { useRouter } from 'next/navigation'
export default function ReRouteButton() {
const router = useRouter()
return <button onClick={() => router.refresh()}>๊ฐ์ ๊ฐฑ์ (๋ธ๋ผ์ฐ์ ์บ์ ๋ถ์๊ธฐ)!</button>
}router.refresh() ๋ ํ๋ฉด ๋ฐฑ์งํ ์์ด ์ค์ง ๋ฐฑ๊ทธ๋ผ์ด๋๋ก ์๋ฒ์ ์ต์ RSC Payload๋ง ์๋ก ์น ๋ด๋ ค๋ฐ์ ์นํํ๋ ๋ง๋ฒ์ ๊ณ ๊ธ ํ
์ด์ผ.
๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
์๋ฌ ๋ฉ์์ง๊ฐ ๋จ๋ฉด Ctrl+F๋ก ๊ฒ์ํด๋ด.
โ Dynamic server usage: cookies ์๋ฌ๊ฐ ๋จ๋ฉด์ ๋น๋๊ฐ ํญํ๋ผ์!
์์ธ: generateStaticParams ์ ๊ฐ์ด '๋ฏธ๋ฆฌ ์ ์ ์ผ๋ก(Static) ๊ตฌ์๋๋ ค๊ณ ์์ 'ํ ํ์ผ ๊ณต๊ฐ ๋ด๋ถ์์, cookies() ๋ URL ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ ๊ฐ์ด ์ค์๊ฐ ๋ฐํ์์๋ง ์ ์ ์๋ ๊ฐ์ฒด๋ฅผ ์ต์ง๋ก ๊บผ๋ด ์ฐ๋ ค๋ค ๋ชจ์์ด ๋ฐ์ํ ๊ฒ.
ํด๊ฒฐ์ฑ
: ํ์ด์ง์ export const dynamic = 'force-dynamic' ์ ๋ช
์์ ์ผ๋ก ์ ์ธํ์ฌ ์ด ํ์ผ์ ์ ๋ ํ์(Static)์ผ๋ก ๋จ์ ์ ์๋ ์ด๋ช
์์ Next.js์๊ฒ ์๋ฐฑํด๋ผ.
๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
์ด ๊ฑฐ๋ํ 4์ค ์ฅ๋ฒฝ์ ๋ฐฉ์ด ์์! ๋ธ๋ผ์ฐ์ ๊ฐ ํ๋ฉด์ ๋ฌ๋ผ๊ณ ์กฐ๋ฅด๋ฉด...
| ๊ป์ง ๊ณ์ธต ์์ | ํ๊ฒจ๋ด๋ ๋จ๊ณ | ์บ์ ์์น | ๋ฌด๋ ฅํ(๋ถ์๊ธฐ) ๋ฐฉ๋ฒ |
|---|---|---|---|
| 1๋ฒ ๋ฐฉ์ด๋ง | Router Cache: "์ผ ๋ธ๋ผ์ฐ์ ์ผ, ๋ ์๊น ์ด๊ฑฐ ๋ฐฉ๋ฌธํ์์์. ๋ด RAM์ ์ ์ฅ๋ ๊ฑฐ ์ค๊ฒ." | ํด๋ผ์ด์ธํธ (๋ธ๋ผ์ฐ์ ) | router.refresh() / ํ์ด์ง ์ฐฝ ๋ซ๊ธฐ / F5 ๊ฐ์ ์ธ๋ก๊ณ ์นจ |
| 2๋ฒ ๋ฐฉ์ด๋ง | Full Route Cache: (์๋ฒ ๋๋ฌ) "์ด? ๋๊ฐ ์ฐพ๋ HTML, ๋ด๊ฐ ์ ๋ฒ ๋น๋ ๋ ์์ ํ ํ์์ผ๋ก ๊ตฌ์๋จ์ง!" | ์๋ฒ (๋์คํฌ) | ์ฌ๋น๋(build) / revalidatePath ์๋ฒ์ก์
ํ๊ฒฉ |
| 3๋ฒ ๋ฐฉ์ด๋ง | Request Memoization: (์ปดํฌ๋ํธ ๋ ๋๋ง ์ค) "์ ๋๊ฐ์ fetch ๋ค? ์ฃผ๋ฐฉ์ฅ ๊ณ ์ํ์ง ๋ง๊ณ ๋ด ๊ธฐ์ต(๋ฉ๋ชจ๋ฆฌ)์์ ๋ณต์ฌํด์ค๊ฒ." | ์๋ฒ (๋ฉ๋ชจ๋ฆฌ ๋จ๋ฐ์ฑ) | ์กฐ์น ํ์ ์์ (1 ๋ฐํ์ ์ข ๋ฃ ์ ์๋ ํ๊ธฐ) |
| 4๋ฒ ๋ฐฉ์ด๋ง | Data Cache: "๊ฒฐ๊ตญ ์ง์ง ๋ฐ์ดํฐ ์ฐ๋ฅด๋ ค ์๋ค? ๊ทผ๋ฐ ๊ทธ API ๊ฒฐ๊ณผ๊ฐ 5์๊ฐ ๋์ ์ผ๋ ค๋ ๋ด DB ์บ์ ์ฐฝ๊ณ ์์ ์ค๊ฒ ใ ใ ใ " | ์๋ฒ (๋์คํฌ) | fetch { cache: no-store } / revalidateTag ๋ฑ |
๐ก ์๋์ด์ ๋ฉํ ๋ชจ๋ธ
๋น๋ ์โ (Static)๋ก ๊ตณ์ด๋ฒ๋ฆฐ ๋ผ์ฐํธ๋ Data Cache๋ฅผ ๋ถ์๊ธฐ ์ ๊น์ง ์ ๋ ์๊ธฐ ์ค์ค๋ก ๊ฐฑ์ ๋์ง ์๋๋ค.
"๋ฐ์ดํฐ๊ฐ ์ ๋ฐ๋์ด์!" ๋ผ๋ ์ง๋ฌธ์ ๋นํฉํ์ง ๋ง๊ณ , ๊ฐ์ฅ ๋จผ์ Vercel ๋น๋ ํฐ๋ฏธ๋ ์ฐฝ์ ์ด๊ณ ๊ทธ ํ์ด์ง๊ฐโ์ธ์งฦ์ธ์ง๋ถํฐ ์์ถํ๋ผ!
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
๋ฐฐ์ ์ผ๋ฉด ํ ๋ฒ ๋นํ์ด์ ํ์ธํด๋ด์ผ ํด.
Q1. ์์ฒ ์ด๊ฐ ํ์ ์ ์ฉ "๋ด ํ๋กํ ํ์ด์ง(/profile/me)"๋ฅผ ๋ง๋ค์๋ค. ์ด ํ์ด์ง ์ต์๋จ ์ปดํฌ๋ํธ์์๋ const token = cookies().get('session_id') ๋ฅผ ํธ์ถํด ์ ์ ์ ์ฟ ํค๋ฅผ ๋ฏ์ด๋ณด๊ณ ์์๋ค. ์์ฒ ์ด๋ ์ด ๋ ๋๋ง ์ฐ์ฐ ๋น์ฉ์ ์ค์ด๊ธฐ ์ํด ์ด ํ์ด์ง๋ฅผ Full Route Cache ํ์์ผ๋ก ์๋ฒฝํ๊ฒ ๊ตฌ์๋ฒ๋ฆฌ๊ณ ์ถ๋ค! ์ด๋ป๊ฒ ํด์ผ ํ ๊น?
โ ์ ๋ต: ๋ถ๊ฐ๋ฅํ๋ค. ๊ตฌ์กฐ์ ์ผ๋ก ์ค๊ณ๊ฐ ํ๋ ค๋จน์๋ค.
๐ก ์์ธ ํด์ค: ๋ด ํ๋กํ(me)์ฒ๋ผ ์ ์ ๋ง๋ค, ์ ์ํ ๋ธ๋ผ์ฐ์ ์ฟ ํค ํค๋๊ฐ์ ๋ฐ๋ผ ์์ ํ ํ๋ฉด์ด ๋ฌ๋ผ์ง๋ ๊ณ ๋์ ๊ฐ์ธํ ํ์ด์ง๋ ์ ๋๋ก Static(์ ์ ) HTML๋ก ํ ๋ฒ๋ง ๊ตฌ์์ ์บ์ฑ์ ๋จน์ผ ์ ์๋ค. cookies() ๋ฑ Dynamic Function์ด ์ฐํ ์๊ฐ ์ด ํ์ด์ง๋ ๋ฌด์กฐ๊ฑด ๋งค ์ ์ ์๋ง๋ค ์๋ก ์กฐ๋ฆฝ๋๋ ฦ (Dynamic) ๋ ๋๋ง์ผ๋ก ๊ฐ๋ฑ๋นํด ์๋ํด์ผ ์ ์์ด๋ค. ์ด๊ฑธ ์ต์ง๋ก ์ ์ ์ผ๋ก ๋ฌป์ด๋ฒ๋ฆฌ๋ ค ํ๋ค๋ฉด ๋๊ตฐ๊ฐ์ ๊ฐ์ธ์ ๋ณด๊ฐ ์ ์ธ๊ณ ์ ์ ์๊ฒ ํ์ ๊ณต์ ๋๋ ๋ํ ์ฐธ์ฌ๊ฐ ๋ฐ์ํ๋ค.
Q2. ์ํธ ๋ฆฌ๋๊ฐ ๋ฉ์ธ ๋ด์ค ํํ์ด์ง(/news)๋ฅผ ๊ตฌ์ถํ๋ค. ์ด๊ฑด ๋ด์ค๋๊น ์ ์ ์ผ๋ก ๊ตฌ์(Full Route Cache - Static) ์๋ต ์๋๋ฅผ ๊ด์์ผ๋ก ๋ฝ์๋๋ฐ, ํธ์งํ์์ ํ๋ฃจ์ ํน์ข
์ด ํฐ์ง ๋๋ง๋ค ๋ด์ค ๋ชฉ๋ก DB๊ฐ ์์๋ก ๋ฐ๋์ด์ผ ํ ๋จ๋ค. ์ํธ๋ ์ด ๋๊ท๋ชจ ๋ด์ค ์ ์ ํ์์ ์ด๋ป๊ฒ, ์ด๋ ์ฃผ๊ธฐ๋ง๋ค ์๋์ผ๋ก ์ชผ๊ฐ๊ณ ๋ค์ ๊ตฝ๊ฒ ํ์ดํ๋ผ์ธ์ ์ธํ
ํด์ผ ์ฐ์ํ ๊น?
โ ์ ๋ต: ๊ฐ์ฅ ์ฐ์ํ ๋ฐฉ๋ฒ์ ๋ ๊ฐ์ง๋ค.
๐ก ์์ธ ํด์ค: ์ฒซ์งธ, ์๊ฐ ํ์์์ ์บ์ฑ (ISR): revalidate: 60 ์์ฑ์ ๋จน์ฌ์, ๋ฑ 60์ด๋ง๋ค ๋ฑ ํ ๋ฒ๋ง ๋ก์ ํ์์ ๋ถ์๊ณ ์ ๋ด์ค ํ์ HTML๋ก ๊ต์ฒดํ๋ค. ํธ์งํ์ 1๋ถ ์ ๋์ ๋๋ ์ด๋ง ์ฒด๊ฐํ๋ฉฐ ํํธํ๋ค. ๋์งธ, ๊ณ ์ค๊ธ ์๋ ์ ๊ฒฉ ๋ฌดํจํ (On-demand Revalidation): Admin CMS ์๋ฒ์์ [๊ธฐ์ฌ ์์ ] ๋ฒํผ์ด ๋๋ฆฌ๋ ๊ทธ ์ฐฐ๋์ ์๊ฐ์๋ง Next.js ์๋ฒ๋ก API ์นํ
ํธ๋ฆฌ๊ฑฐ๋ฅผ ๋ ๋ ค, ์๋ฒ ์ก์
์ revalidatePath('/news') ๋ง๋ฒ ํจ์๋ฅผ ํ๊ณต์ ์๊ฒ ๋ง๋ ๋ค. ์ด๋ ๊ฒ ๋๋ฉด ํ์์๋ ์๋ฒฝํ Static ์๋๋ก ๊ฟ์ ๋นจ๋ค๊ฐ, ์ค์ง ํธ์ง์์ ์กฐ๋ฆฌ ๋๊ตฌ๊ฐ ๋๋ฆฐ ๊ทธ ์๊ฐ์๋ง ํ์ ์ฅ๋ง์ด ๋ฐ์ด ๋๋ ๊ถ๊ทน์ ์ํคํ
์ฒ ์์ฑ! (์ด ๋ง์ ์ ์ฌํ 3์ฅ์์ ๋ ๊น๊ฒ ํ ๊ฒ์ด๋ค.)
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋์ ์ ๋ง ๋ฅ์คํธ์ ๊ฑฐ๋ํ '์บ์ฑ ์ฅ๋ฒฝ' ์์ ์ ๊ธฐ๋ถ์ด์์ด. ๊ทธ๋์ ๋จ์ํ "์บ์๊ฐ ์์ด์ ๋นจ๋ผ์"๋ผ๊ณ ๋ง ์๊ฐํ๋๋ฐ, ๊ทธ ์ด๋ฉด์ Router, Full Route, Request Memoization, Data Cache๋ผ๋ 4์ค ํํฐ๊ฐ ์์๋ค๋!
๐ก ์ค๋์ ๊ตํ: "๋ฐ์ดํฐ๊ฐ ์ ๋ฐ๋๋ค๋ฉด ๋น๋ ์์ ์ ํ์(Static)์ผ๋ก ๊ตฝ๊ณ , ์ ์ ๋ง๋ค ๋ค๋ฅธ ์ ๋ณด๋ ๋์ (Dynamic)์ผ๋ก ๋ ๋๋งํ์ฌ ๋ณด์๊ณผ ์ฑ๋ฅ์ ๋ชจ๋ ์ก์!"
์ํธ ๋ฆฌ๋ ๋์ด ๋ธ๋ผ์ฐ์ RAM๋ถํฐ ์๋ฒ ๋์คํฌ๊น์ง ๋จ๊ณ๋ณ๋ก ์บ์ฑ์ด ์๋ํ๋ ์๋ฆฌ๋ฅผ ์ค๋ช ํด ์ฃผ์ค ๋, ํํธํ๋์ด ์๋ ์ง์๋ค์ด ํ๋๋ก ํฉ์ณ์ง๋ ์ง๋ฆฟํจ์ ๋๊ผ์ด. "ํ์์ด ๋ HTML์ ๋ถ์๊ธฐ ์ ๊น์ง ๋ณํ์ง ์๋๋ค"๋ ๋ง์์ ๊ฐ์ด์ ์๊ธฐ๋ฉฐ... ์ค๋ ๋จธ๋ฆฌ๋ฅผ ๋๋ฌด ๋ง์ด ์ผ๋๋ ๋น๋ถ์ด ํ์ํด. ์ง์ ๊ฐ๋ ๊ธธ์ ๋ฌ์ฝคํ ๋ง์นด๋กฑ์ด๋ผ๋ ์ข ์ฌ ๊ฐ์ผ์ง. ๋ด์ผ์ ๋ 'ํจ์จ์ ์ธ' ์บ์ฑ ์ ๋ต์ ์ง๋ ๊ฐ๋ฐ์๊ฐ ๋๊ฒ ์ด! ๐ฃ