๐Ÿš€ Next.js ์‹ฌํ™” 2์žฅ: `use cache`์™€ ๋ถ€๋ถ„ ์บ์‹ฑ (Next.js 15+ ์ตœ์‹  ํŠธ๋ Œ๋“œ)

๐Ÿ“‹ ๊ฐœ์š”

Next.js 15+์˜ use cache ์ง€์‹œ์–ด์™€ ๋ถ€๋ถ„ ์บ์‹ฑ์œผ๋กœ ์„ธ๋ฐ€ํ•œ ์บ์‹ฑ ์ „๋žต์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 15๋ถ„ (์ „์ฒด) / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 8๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

  • ์˜์ฒ (์‹ ์ž…): "Next.js 14์—์„œ 15๋กœ ๋ฒ„์ „์„ ์˜ฌ๋ ธ๋”๋‹ˆ, ์˜›๋‚ ์ฒ˜๋Ÿผ ๋ฌด์‹ํ•˜๊ฒŒ ํ‰์ƒ ์บ์‹ฑ์„ ์•ˆ ํ•ด์ค˜์„œ ์‚ฌ์ดํŠธ๋Š” ์‹ ์„ ํ•ด์กŒ๋Š”๋ฐ ๋””๋น„ ํ†ต์‹  ๋น„์šฉ์ด ๋„ˆ๋ฌด ๋‚˜์™€์š”! ๋ฌด๊ฑฐ์šด ํ†ต๊ณ„ ์ปดํฌ๋„ŒํŠธ๋Š” ๋‹ค์‹œ 14 ์‹œ์ ˆ์ฒ˜๋Ÿผ ์„œ๋ฒ„์— ์–ผ๋ ค๋‘๊ณ  ์‹ถ์€๋ฐ, fetch ์˜ต์…˜์— ๋•์ง€๋•์ง€ cache: 'force-cache' ๋ถ™์ด๋Š” ๊ฑฐ ๋ง๊ณ  ์ข€ ์šฐ์•„ํ•œ ๋ฐฉ๋ฒ• ์—†์„๊นŒ์š”?"
  • ์˜ํ˜ธ(๋ฆฌ๋“œ): "์˜์ฒ  ๋‹˜... Vercel ๋†ˆ๋“ค์ด 15๋ฒ„์ „๋ถ€ํ„ฐ ๋˜์ ธ์ค€ ๊ฐ•๋ ฅํ•œ ๋งˆ๋ฒ•, 'use cache' ๋””๋ ‰ํ‹ฐ๋ธŒ๋ฅผ ์จ๋ณด์„ธ์š”! ํŒŒ์ผ ๋งจ ์œ„, ํ•จ์ˆ˜ ๋ฐ”๋กœ ์œ„, ์‹ฌ์ง€์–ด ํŠน์ • ์ปดํฌ๋„ŒํŠธ ๋ธ”๋ก ์œ„์— ์„ ์–ธํ•˜๋Š” ์ˆœ๊ฐ„ ๊ทธ ์กฐ๊ฐ ๋ฉ์–ด๋ฆฌ๋งŒ ํ•€์…‹์œผ๋กœ ์ง‘์–ด์„œ ์˜์›ํ•œ ๋น™ํ•˜ ์†(Data Cache)์— ํ™”์„์œผ๋กœ ๋งŒ๋“ค์–ด๋ฒ„๋ฆฐ๋‹ค๊ณ ์š”!"

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„
Next 15์˜ ์บ์‹œ ์„ ์–ธํ˜• ์‹œ๋Œ€ โ†’ use cache์˜ ์Šค์ฝ”ํ”„ ์ ์šฉ๋ฒ• โ†’ ๋Œ€๋ง์˜ PPR (์ ˆ๋ฐ˜๋งŒ ํ™”์„ ๋งŒ๋“ค๊ธฐ) ๊ธฐ์ˆ 

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • ๋ฌด๊ฑฐ์šด ์จ๋“œํŒŒํ‹ฐ SDK๋‚˜ ์ง์ ‘ ์ง  ์Šฌ๋กœ์šฐ ์ฟผ๋ฆฌ ํ•จ์ˆ˜ ์ž์ฒด์— 'use cache' ์ง€์‹œ์ž๋ฅผ ๋‹ฌ์•„ ์ปดํฌ๋„ŒํŠธ ๋ ˆ๋ฒจ์˜ ์บ์‹ฑ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ง์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ •์ ์ธ ๊ป๋ฐ๊ธฐ ์•ˆ์— ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ํ’ˆ์€ <Suspense> ๊ตฌ์—ญ์„ ์„ž์–ด, PPR ์•„ํ‚คํ…์ฒ˜์˜ ํ˜๋ช…์ ์ธ TTFB(0.1์ดˆ ์ดˆ๊ธฐ ๋กœ๋”ฉ)๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€

์‹ฌํ™” 1์žฅ์—์„œ ์šฐ๋ฆฌ๋Š” ๋”์ฐํ•œ ์บ์‹œ ์ง€์˜ฅ์„ ๋ดค์–ด. Next.js 13, 14 ๋ฒ„์ „์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“  fetch๋ฅผ ํ‰์ƒ ์บ์‹ฑํ•ด๋ฒ„๋ฆฌ๋Š” ๊ฑฐ๋Œ€ํ•œ ํญ๊ตฐ์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด์ง€.
์›์„ฑ์ด ๋„ˆ๋ฌด ๋น—๋ฐœ์น˜์ž, Next.js 15๋ฒ„์ „๋ถ€ํ„ฐ๋Š” ํŒจ๋Ÿฌ๋‹ค์ž„์„ "๊ธฐ๋ณธ์€ ๋งค๋ฒˆ ์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋™์ (Dynamic) ํ†ต์‹ ์ด๋‹ค! ๋‹ˆ๊ฐ€ ์ง„์ •์œผ๋กœ ์›ํ•  ๋•Œ๋งŒ ์บ์‹œ๋ฅผ ์„ ์–ธํ•ด๋ผ(opt-in)!" ๋กœ ์•„์˜ˆ 180๋„ ๋’ค์ง‘์–ด๋ฒ„๋ ธ์–ด.

์ด ์ƒˆ๋กœ์šด ์„ ์–ธ์  ์บ์‹ฑ ์‹œ๋Œ€์—์„œ ํ”„๋ก ํŠธ ์‹œ๋‹ˆ์–ด๋“ค์—๊ฒŒ ๊ฐ€์žฅ ํ•ซํ•œ ๋ฌด๊ธฐ๊ฐ€ ๋ฐ”๋กœ ์‹ฌํ™” 2์žฅ์˜ ์ฃผ์ œ์ธ 'use cache' ์˜ˆ์•ฝ์–ด์™€ PPR(๋ถ€๋ถ„ ๋ Œ๋”๋ง) ์ด์•ผ.

์ด๊ฑธ ๋ชจ๋ฅด๋ฉด ๋„ˆ๋Š” Next 15 ์„ธ์ƒ์—์„œ ๋ชจ๋“  ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋น„์‹ผ CPU ๋ Œ๋”๋ง์œผ๋กœ ๋Œ๋ฆฌ๋‹ค๊ฐ€ ํด๋ผ์šฐ๋“œ ์š”๊ธˆ ํญํƒ„์„ ๋งž๊ณ  ํšŒ์‚ฌ์—์„œ ์ง์„ ์‹ธ๊ฒŒ ๋ ์ง€๋„ ๋ชฐ๋ผ. ํ™”๋ฉด ์ „์ฒด ๋‹จ์œ„๊ฐ€ ์•„๋‹Œ, "๋‚ด๊ฐ€ ์›ํ•˜๋Š” ์กฐ๊ฐ"๋งŒ ์–ผ๋ ค๋ฒ„๋ฆฌ๋Š” ์ตœ์‹  ํ•€์…‹ ๊ธฐ๋ฒ•์„ ์ง€๊ธˆ๋ถ€ํ„ฐ ๋งˆ์Šคํ„ฐํ•˜์ž.


๐Ÿ—๏ธ ๋น„์œ ๋กœ ๋จผ์ € ์ดํ•ดํ•˜๊ธฐ

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด? (์˜ํ˜ธ์˜ ๋น™๊ฒฐ ๋งˆ๋ฒ•์ฑ…)

โŒ ๊ณผ๊ฑฐ 14๋ฒ„์ „ (๋ˆˆ๋ณด๋ผ ๊ด‘์—ญ ๋งˆ๋ฒ•)
๋งˆ์„ ์ „์ฒด(ํŽ˜์ด์ง€ ํ†ต์งธ๋กœ)์— ๋ˆˆ๋ณด๋ผ๋ฅผ ๋‚ด๋ ค์„œ ์˜จ ๋™๋„ค๋ฅผ ์–ผ๋ ค๋ฒ„๋ ธ์–ด. ๋‹ค ๊ฝ๊ฝ ์–ผ์–ด๋ถ™์–ด์„œ ์žฅ์‚ฌ๋Š” ๋น ๋ฅธ๋ฐ ๋ˆˆ์‚ฌ๋žŒ์ด ์•ˆ ์›€์ง์—ฌ์„œ ๋‹ต๋‹ตํ–ˆ์–ด.

โœ… ํ˜„์žฌ 15๋ฒ„์ „ (ํ•€์…‹ ๋น™๊ฒฐ ๋งˆ๋ฒ• 'use cache')
์ด์ œ ์˜ํ˜ธ ๋ฆฌ๋“œ๊ฐ€ ์š”์ˆ  ์ง€ํŒก์ด ํ•˜๋‚˜๋ฅผ ๋“ค๊ณ  ๋‹ค๋…€.
"์–!" ํ•˜๊ณ  ์ง€ํŒก์ด('use cache')๋กœ ์น˜๋Š” ํŠน์ • ์ƒ์ž(ํ•จ์ˆ˜, ์ปดํฌ๋„ŒํŠธ)๋งŒ ๊ฝ๊ฝ ์–ผ์–ด๋ถ™์–ด์„œ (์บ์‹œ ์„œ๋ฒ„์— ์ €์žฅ), ๋‹ค์Œ๋ฒˆ ๋ถ€๋ฅผ ๋• ์—ฐ์‚ฐ 0์ดˆ ๋งŒ์— ์™„์ œํ’ˆ์ด ํŠ€์–ด๋‚˜์™€! ๋งˆ์„ ์‚ฌ๋žŒ๋“ค(๋‚˜๋จธ์ง€ ๋™์  ์š”์†Œ)์€ ์ถฅ์ง€ ์•Š๊ณ  ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ง‰ ๋›ฐ์–ด๋‹ค๋‹ ์ˆ˜ ์žˆ์ง€.


๐Ÿงฉ use cache ๋””๋ ‰ํ‹ฐ๋ธŒ: ํ•จ์ˆ˜ ์ˆ˜์ค€์˜ ์˜๊ตฌ ๋™๊ฒฐ ๐ŸŸข

'use client' ๋‚˜ 'use server' ์ฒ˜๋Ÿผ ํŒŒ์ผ์ด๋‚˜ ํ•จ์ˆ˜ ์Šค์ฝ”ํ”„ ์ตœ์ƒ๋‹จ์— ๋งˆ๋ฒ•์˜ ๋ถ€์ ์„ ๋ถ™์ด๋Š” ๋ฐฉ์‹์ด์•ผ.
์ฃผ์˜: ์•„์ง Next.js ๊ณต์‹ ์‹คํ—˜์‹ค(Experimental) ์˜ต์…˜์„ ์ผœ์•ผ ์™„์ „ํžˆ ๋™์ž‘ํ•˜๋Š” ์ตœ์ฒจ๋‹จ ์ŠคํŽ™์ด๋‹ค.

์ƒํ™ฉ: ๋ฌด๊ฑฐ์šด ํ†ต๊ณ„ ๋ถ„์„ ํ•จ์ˆ˜ (fetch๊ฐ€ ์•„๋‹˜!)

์˜์ฒ ์ด๊ฐ€ ์งœ๋†“์€ ๋”์ฐํ•˜๊ฒŒ ๋А๋ฆฐ 5์ดˆ์งœ๋ฆฌ ์›”๊ฐ„ ์ง‘๊ณ„ ์ฟผ๋ฆฌ๊ฐ€ ์žˆ์–ด. fetch๋กœ ์ฐŒ๋ฅด๋Š” ์™ธ๋ถ€ API๊ฐ€ ์•„๋‹ˆ๋ผ, ์ž๊ธฐ๊ฐ€ ์ง์ ‘ Node.js ํ™˜๊ฒฝ์—์„œ DB ํด๋ผ์ด์–ธํŠธ ๋ชจ๋“ˆ(Prisma ๋“ฑ)๊ณผ ๊ฑฐ๋Œ€ํ•œ for ๋ฐ˜๋ณต๋ฌธ์œผ๋กœ ์ง  ํ•จ์ˆ˜์ง€.

// app/lib/stats.ts
// ๐Ÿ’ฃ ์ด๋†ˆ์€ fetch๊ฐ€ ์•„๋‹ˆ๋ผ์„œ ๊ธฐ๋ณธ Next Data Cache ์€์ด์„ ๋ฐ›์„ ์ˆ˜ ์—†์–ด ๋งค๋ฒˆ 5์ดˆ๊ฐ€ ๊ฑธ๋ฆผ!
export async function getHeavyMonthlyStats(year: number, month: number) {
  const rawData = await db.query(...); 
  const result = heavyCPUCalculate(rawData); // ์—ฌ๊ธฐ์„œ 5์ดˆ๊ฐ„ ๋™๋™๋Œ...
  return result;
}

โœ… ๋น™๊ฒฐ ๋ถ€์  ๋ถ™์ด๊ธฐ ('use cache')

๋†€๋ž๊ฒŒ๋„ ์ฝ”๋“œ ๋‹จ ํ•œ ์ค„ ์ด๋ฉด เฆเฆ‡ 5์ดˆ์งœ๋ฆฌ ์ง€์˜ฅ์„ 0.01์ดˆ(ํ™”์„ ๊บผ๋‚ด๊ธฐ)๋กœ ๋‹จ์ถ•ํ•  ์ˆ˜ ์žˆ์–ด!

// app/lib/stats.ts
 
export async function getHeavyMonthlyStats(year: number, month: number) {
  'use cache'; // ๐ŸงŠ ๋น™๊ฒฐ ๋งˆ๋ฒ• ๋ฐœ๋™! ์ด์ œ๋ถ€ํ„ฐ ์ด ํ•จ์ˆ˜์˜ ๋ฆฌํ„ด๊ฐ’์€ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํ‚ค๊ฐ’ ์‚ผ์•„ Data Cache ๋ ˆ๋ฒจ๋กœ ์˜์›ํžˆ ์–ผ๋ ค์ง!!
 
  const rawData = await db.query(...); 
  const result = heavyCPUCalculate(rawData); 
  return result;
}

[๋งˆ๋ฒ•์˜ ๋ถ€์ž‘์šฉ (๋ถ€์ˆ˜๊ธฐ)]
์ด๊ฑฐ ์‹ฌํ™” 1์žฅ์˜ React.cache() (1์ดˆ ๋‹จ๋ฐœ์„ฑ ์ง€์šฐ๊ฐœ) ๋ž‘ ์ฐฉ๊ฐํ•˜๋ฉด ์•ˆ ๋ผ!
'use cache'๋Š” ์˜๊ตฌ ๋ณด์กด๋˜๋Š” 3๋‹จ๊ณ„ Data Cache ์žฅ๋ฒฝ์— ํ™”์„์„ ํŒŒ๋ฌป๋Š” ํ–‰์œ„์•ผ!
์ฆ‰, ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ์–ด๋“œ๋ฏผ ํŽ˜์ด์ง€์—์„œ ๋ฐฑ๋‚ ์ฒœ๋‚  ๋งค์ถœ์„ ๊ฐฑ์‹ ํ•ด๋„ ์œ ์ €๋Š” ์˜์›ํžˆ 5์ดˆ ์ „์˜ (์–ผ๋ ค์ง„) ํ†ต๊ณ„๋งŒ ๋ณด๊ฒŒ ๋ผ.
ํ™”์„์„ ๋ถ€์ˆ˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ? ์šฐ๋ฆฌ๊ฐ€ ๋ฐฐ์šธ ๊ฐ•๋ ฅํ•œ ์ €๊ฒฉ์ด, revalidateTag ๋ฅผ ํ•จ์ˆ˜์— ๋ช…์ฐฐ๋กœ ๋‹ฌ์•„์ฃผ๊ณ  ์ด๋ฒ„๋ ค์•ผ ํ•ด. (์ด๊ฑด ์‹ฌํ™” 3์žฅ์—์„œ ๋” ๊นŠ์ด ๋‹ค๋ฃฐ ์˜ˆ์ •)


๐ŸŒฑ ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„ ์บ์‹ฑ (Cache Components) ๐ŸŸก

ํ•จ์ˆ˜ ๋ง๊ณ , HTML ํƒœ๊ทธ๋ฅผ ๋ฟœ์–ด๋‚ด๋Š” '๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ(export default function...)' ์ž์ฒด๋„ ํ†ต์งธ๋กœ ์–ผ๋ ค๋ฒ„๋ฆด ์ˆ˜ ์žˆ์–ด!

์ƒํ™ฉ: DB์—์„œ ๊ฐ€์ ธ์˜จ 1๋งŒ ๊ฐœ ๋ชฉ๋ก ๊ทธ๋ฆฌ๊ธฐ

// app/components/MegaList.tsx
 
// ๐ŸงŠ ์ด ์ปดํฌ๋„ŒํŠธ ๋ฉ์–ด๋ฆฌ๊ฐ€ ๋ฟœ์–ด๋‚ด๋Š” "HTML ๊ตฌ์กฐ" ๊ฒฐ๊ณผ๋ฌผ ์ž์ฒด๋ฅผ ํ†ต์งธ๋กœ ์–ผ๋ ค๋ฒ„๋ฆฐ๋‹ค!
export default async function MegaList({ categoryId }: { categoryId: string }) {
  'use cache'; 
  
  // 1๋งŒ ๊ฐœ ๋ ˆ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์™€์„œ ์ˆ˜๋งŒ ๊ฐœ์˜ <li> ํƒœ๊ทธ๋กœ ์กฐ๋ฆฝํ•˜๋Š” ๊ฑฐ๋Œ€ํ•œ CPU ์—ฐ์‚ฐ
  const data = await db.getItems(categoryId);
 
  return (
    <ul>
      {data.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  )
}

์ด์ œ page.tsx ๋“ฑ ์•„๋ฌด ๊ณณ์—์„œ๋‚˜ ์ € <MegaList /> ๋ฅผ ์ˆ˜๋ฐฑ ๋ฒˆ ๋ Œ๋”๋งํ•˜๋„๋ก ํ˜ธ์ถœํ•ด๋ด๋„, ์„œ๋ฒ„๋Š” HTML ํƒœ๊ทธ ๋ฌธ์ž์—ด ์กฐ๋ฆฝ์„ ์•„์˜ˆ ์ƒ๋žตํ•˜๊ณ  ๋””์Šคํฌ ์–ด๋”˜๊ฐ€ ์บ์‹œ ์ฐฝ๊ณ ์—์„œ ๊บผ๋‚ธ ์™„์„ฑํ’ˆ ํ…์ŠคํŠธ ๋ฉ์–ด๋ฆฌ๋ฅผ 0.01์ดˆ ๋งŒ์— ๋ธŒ๋ผ์šฐ์ €์— ๋ฐฐ๋‹ฌํ•ด์ค˜.
ํ”„๋ก ํŠธ ์„œ๋ฒ„ ์š”๊ธˆ์„ ํš๊ธฐ์ ์œผ๋กœ ๋‚ฎ์ถ”๋Š” 2026 ํŠธ๋ Œ๋“œ์˜ ํ•ต์‹ฌ ๊ธฐ๋ฒ•์ด์ง€.


๐Ÿ›ก๏ธ PPR (Partial Pre-Rendering): ์ •์  ๊ป๋ฐ๊ธฐ + ๋™์  ์†์‚ด ๐Ÿ”ด

์ž, ์ด์ œ ๊ถ๊ทน์˜ ํ€˜์ŠคํŠธ. "Next.js ๋ Œ๋”๋ง ์ตœ์ •์ ์˜ ๊ธฐ์ˆ "๋กœ ๋„˜์–ด๊ฐ€์ž.

๋ชจ์ˆœ๋˜๋Š” ์š”๊ตฌ์‚ฌํ•ญ

๊ธฐํš์ž๊ฐ€ ์™”์–ด.
"๊ฐœ๋ฐœ์ž๋‹˜! ์šฐ๋ฆฌ ์‡ผํ•‘๋ชฐ ๋ฉ”์ธ์˜ ์ƒ๋‹จ GNB(๋ฉ”๋‰ด)๋ž‘ ์ „์ฒด ํ‹€์€ ์•„๋ฌด๋„ ์•ˆ ๊ฑด๋“œ๋ฆฌ๋‹ˆ๊นŒ ๋น ๋”ฑ๋น ๋”ฑ ๊ด‘์†(Static)์œผ๋กœ ๋œจ๊ฒŒ ํ•ด์ฃผ์‹œ๊ณ ์š”.
๋Œ€์‹  ํ•œ๊ฐ€์šด๋ฐ ๋ฐ•ํ˜€ ์žˆ๋Š” [์‚ฌ์šฉ์ž ์žฅ๋ฐ”๊ตฌ๋‹ˆ ์˜์—ญ]์€ ๋ฐฉ๋ฌธ๊ฐ ์ด๋ฆ„์ด๋ž‘ ๊ฐœ์ธ ์ฟ ํ‚ค๋งˆ๋‹ค ์ „๋ถ€ ๋‹ค๋ฅด๊ฒŒ(Dynamic) ๋– ์•ผ ํ•ด์š”!"

์‹ฌํ™” 1์žฅ์—์„œ ๋ฐฐ์› ์ง€? "๋‹จ ํ•œ ์ค„์˜ ์‹ค์‹œ๊ฐ„ ์ฟ ํ‚ค๋ผ๋„ ์ฝ์œผ๋ฉด ๊ทธ ํŽ˜์ด์ง€์˜ ํ™”์„(Static)์€ ์˜์›ํžˆ ๋ฐ•์‚ด ๋‚˜๊ณ  ์ „์ฒด๊ฐ€ SSR(Dynamic)๋กœ ๊ฐ•๋“ฑ๋œ๋‹ค" ๊ณ !

โœ… PPR (๋ถ€๋ถ„ ์‚ฌ์ „ ๋ Œ๋”๋ง) ์ด๋ผ๋Š” ์ด๋‹จ์•„

Vercel ์ฒœ์žฌ๋“ค์ด ์ด๊ฑธ ๊ทน๋ณตํ•ด๋ƒˆ์–ด!
์šฐ๋ฆฌ๊ฐ€ ๊ฐ€์ด๋“œ 7์žฅ์—์„œ ๋ฐฐ์šด <Suspense> ๊ตฌ๋ฉ ๋šซ๊ธฐ ๊ธฐ์ˆ ๊ณผ ๊ฒฐํ•ฉ ๋œ ๊ดด๋ฌผ ์‚ฌ์–‘์ด์•ผ.

// app/page.tsx
// โš ๏ธ ์ด ํŽ˜์ด์ง€ ์•ˆ์— ์‹ค์‹œ๊ฐ„ ์ •๋ณด(์ฟ ํ‚ค ๋“ฑ)๊ฐ€ ์žˆ์ง€๋งŒ ๋‚œ ์ ˆ๋Œ€ ์†๋„ ๊ฐ•๋“ฑ์„ ๋‹นํ•˜์ง€ ์•Š์„ ํ…Œ๋‹ค!
 
import { Suspense } from 'react'
import { cookies } from 'next/headers'
import StaticHeader from './StaticHeader'
 
// 1. ์—ฌ๊ธฐ์„œ ๋™์  (๋งค ์ ‘์†๋งˆ๋‹ค ๋‹ฌ๋ผ์ง€๋Š” ๊ฐœ์ธํ™”) ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ ๋ฉ์–ด๋ฆฌ๋ฅผ ๋ถ„๋ฆฌํ•œ๋‹ค!
async function PersonalCart() {
  const token = (await cookies()).get('token'); // ๐Ÿ’ฃ ์›๋ž˜๋ผ๋ฉด ์ „์ฒด ํŽ˜์ด์ง€ ํ™”์„์„ ๊นจ๋ถ€์ˆ˜๋Š” ํญํƒ„!!
  const cartInfo = await fetchCart(token);
  return <div>๋‚ด ์žฅ๋ฐ”๊ตฌ๋‹ˆ: {cartInfo.count}๊ฐœ</div>
}
 
 
// 2. ์ „์ฒด ์กฐํ•ฉํ•˜๋Š” ๋ฉ”์ธ ์ปดํฌ๋„ŒํŠธ ๋ ˆ์ด์•„์›ƒ
export default function HomePage() {
  return (
    <main>
      {/* ๐ŸŒŸ ์—ฌ๊ธฐ๋Š” ๋นŒ๋“œ ํƒ€์ž„์— ํ‰์ƒ ๋ณด์กด๋˜๋Š” ํ™”์„(Static)์œผ๋กœ ๊นก๊นก ์–ผ์–ด์„œ 0.1์ดˆ ๋งŒ์— ์„œ๋น™๋จ! */}
      <StaticHeader />
      
      {/* ๐ŸŒŸ ๋™์  ํญํƒ„ ๊ตฌ์—ญ์„ Suspense ์บก์А๋กœ ๊ฒฉ๋ฆฌํ•ด ๊ฐ€๋‘ฌ๋ฒ„๋ ธ๋‹ค! */}
      <Suspense fallback={<div>์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋กœ๋”ฉ์ค‘...</div>}>
        <PersonalCart />
      </Suspense>
      
      {/* ๐ŸŒŸ ์—ฌ๊ธฐ๋„ Static! */}
      <Footer />
    </main>
  )
}

[๋งˆ๋ฒ•์˜ ์ž‘๋™ ์›๋ฆฌ (PPR ์บ์‹ฑ ๊ฒฐํ•ฉ)]

  1. ๋นŒ๋“œ ๋ช…๋ น(next build) ์‹œ, ๋„ฅ์ŠคํŠธ ๋ด‡์ด ํŠธ๋ฆฌ๋ฅผ ์ˆœํšŒํ•˜๋‹ค๊ฐ€ Suspense ์บก์А์„ ๋งŒ๋‚˜๋ฉด "์•„ํ•˜! ์ด ๊ตฌ๋ฉ(Hole) ์•ˆ์ชฝ์€ ํญํƒ„ ์ง€๋Œ€๋‹ˆ๊นŒ ๋‚ด๊ฐ€ ๊ฑด๋“œ๋ฆฌ๋ฉด ์•ˆ ๋˜๊ฒ ๊ตฐ. ๋Œ€์‹  ์บก์А ๋ฐ”๊นฅ์˜ ๊ป๋ฐ๊ธฐ ๋ ˆ์ด์•„์›ƒ๋“ค์€ ๋ชจ์กฐ๋ฆฌ ์ •์  ํ™”์„ ์Šค์ผˆ๋ ˆํ†ค(Static)์œผ๋กœ ์ž”๋œฉ ๊ตฌ์›Œ๋‘์ž!" ํ•˜๊ณ  ๋ถ€๋ถ„ ํ™”์„์„ ๋งŒ๋“ค์–ด๋‚ด.
  2. ์œ ์ €๊ฐ€ ์ ‘์†ํ•˜๋ฉด? 0.1์ดˆ ๋งŒ์— ๊ทธ ๊ฐ€์šด๋ฐ์— ๊ตฌ๋ฉ์ด ๋ปฅ ๋šซ๋ฆฐ ํ™”์„ ๊ป๋ฐ๊ธฐ(GNB + Footer + ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋กœ๋”ฉ์ค‘ ๊ธ€์”จ)๊ฐ€ ๋ฌด์กฐ๊ฑด ์ฆ‰๊ฐ ์„œ๋น™(TTFB ํญ๋ฐœ)๋จ!!
  3. ๊ทธ์™€ ๋™์‹œ์— ์บก์А ์•ˆ์ชฝ์˜ ์‚ด์‚ด ๋…น๋Š” ๋™์  ์žฅ๋ฐ”๊ตฌ๋‹ˆ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋’ค๋Šฆ๊ฒŒ ์ฟ ํ‚ค๋ฅผ ๊นŒ๋ณด๊ณ  ์„œ๋ฒ„ ์—ฐ์‚ฐ์„ ๋งˆ์นœ ๋’ค ๊ทธ ๋นˆ ๊ตฌ๋ฉ์„ ์ฑ„์šฐ๋Ÿฌ Stream ์ „์†ก๋œ๋‹ค.

์ด๊ฒƒ์ด Next.js๊ฐ€ ๋ฐ€์–ด๋ถ™์ด๋Š” ๊ถ๊ทน์˜ ์•„ํ‚คํ…์ฒ˜, ๊ป๋ฐ๊ธฐ๋Š” ํ™”์„์œผ๋กœ! ์†์‚ด์€ ๋ฌดํ•œํ•œ ๋™์  ๋ฐ์ดํ„ฐ๋กœ! "Partial Pre-Rendering(PPR)" ์˜ ์‹ค์ฒด์•ผ.


๐Ÿ’ฅ ์—๋Ÿฌ ํ•ด๊ฒฐ ์นดํƒˆ๋กœ๊ทธ

์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋œจ๋ฉด Ctrl+F๋กœ ๊ฒ€์ƒ‰ํ•ด๋ด.

โŒ 'use cache' is an experimental feature...

์›์ธ: next.config.ts ๊ณ„๊ธฐํŒ์— ์•„์ง ์‹คํ—˜์‹ค ์˜ต์…˜์„ ์•ˆ ์ผœ๊ณ  ์ด 15๋ฒ„์ „ ์‹ ๋ฌ˜ํ•จ์„ ์“ฐ๋ ค๊ณ  ์‹œ๋„ํ•จ.
ํ•ด๊ฒฐ์ฑ…: ๊ณ„๊ธฐํŒ ์„ค์ • ํŒŒ์ผ์— ๋“ค์–ด๊ฐ€ experimental: { dynamicIO: true } ์„ค์ • ๋“ฑ ๊ด€๋ จ Next.js 15+ ์ตœ์‹  ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ๋ฅผ ์ฐธ๊ณ ํ•ด ํ”Œ๋ž˜๊ทธ๋ฅผ ์ˆ˜๋™ ์ ๊ฒ€ํ•ด์•ผ ํ•œ๋‹ค.

โŒ Suspense ์บก์А์„ ์ž˜ ์”Œ์› ๋Š”๋ฐ ๋นŒ๋“œํ•ด๋ณด๋ฉด ์ž๊พธ ์†์‚ด๊นŒ์ง€ ๋‹ค ๊นŒ๋ฐœ๋ ค์ง„ ์ฑ„ ์ „์ฒด ํŽ˜์ด์ง€๊ฐ€ Dynamic์œผ๋กœ ๊ฐ•๋“ฑ๋‹นํ•ด๋ฒ„๋ ค์š” ใ… ใ… 

์›์ธ: ์•„์ฃผ ํ”ํ•œ ์‹ค์ˆ˜.
ํญํƒ„ ์ฝ”๋“œ(const t = cookies().get())๋ฅผ ์บก์А์˜ ๋ฐ”๊นฅ(๋ถ€๋ชจ)์ธ page.tsx ๋‚ด๋ถ€ ์Šค์ฝ”ํ”„์— ๋ฉ๊ทธ๋Ÿฌ๋‹ˆ ์„ ์–ธํ•ด ๋‘๊ณ , ๊ทธ๊ฑธ ์†์‚ด ์ปดํฌ๋„ŒํŠธ์˜ Props๋กœ ๋‚ด๋ ค๋ณด๋‚ด๋Š” ์šฐ๋ฅผ ๋ฒ”ํ–ˆ๊ธฐ ๋•Œ๋ฌธ.
๋ถ€๋ชจ๊ฐ€ ์ฟ ํ‚ค ํญํƒ„์— ๋…ธ์ถœ๋œ ์ˆœ๊ฐ„ ๋ถ€๋ชจ์˜ ๊ป๋ฐ๊ธฐ๋„ ํ•จ๊ป˜ ์˜ค์—ผ๋˜์–ด ์˜์›ํžˆ ํ™”์„ ๊ธฐ๋Šฅ์„ ์ƒ์‹คํ•œ๋‹ค.
ํ•ด๊ฒฐ์ฑ…: ๋ฌด์กฐ๊ฑด ์†์‚ด ์ปดํฌ๋„ŒํŠธ ์Šค์ฝ”ํ”„ ๋‚ด๋ถ€ ์•ˆ์ชฝ์œผ๋กœ cookies()๋‚˜ ๋ฆฌํ€˜์ŠคํŠธ ํ—ค๋” ์ฝ๊ธฐ ๋ช…๋ น๋ฌธ์„ ํ†ต์งธ๋กœ ๊ฒฉ๋ฆฌ/์ด์ „์‹œ์ผœ์•ผ PPR์ด ํญํƒ„์„ ์ œ๋Œ€๋กœ ํ”ผํ•ด ๊ฐ„๋‹ค.


๐Ÿ ์ด๋ฒˆ์— ๋ฐฐ์šด ๋‚ด์šฉ ์ด์ •๋ฆฌ

๊ธฐ์ˆ  ๋„๊ตฌํšจ๊ณผ์žฅ๋‹จ์  ๋ฐ ์‚ฌ์šฉ์ฒ˜
'use cache'๋А๋ฆฐ API๋‚˜ ๋ฌด๊ฑฐ์šด ํ•จ์ˆ˜, ์ปดํฌ๋„ŒํŠธ UI๋ฅผ ํ†ต์งธ๋กœ ์–ผ๋ฆฐ๋‹ค (DB ๋ถ€ํ•˜ 0)15+ ์ตœ์‹  ๋ฒ„์ „์—๋งŒ ํ†ต์ฐฐ๋˜๋ฉฐ, ๋‚ก์€ ๋ฐ์ดํ„ฐ๋ฅผ ์–ธ์ œ ๋ถ€์ˆ ์ง€(Invalidate)์— ๋Œ€ํ•œ ์™„๋ฒฝํ•œ ์„ค๊ณ„๊ฐ€ ์„ ํ–‰๋˜์–ด์•ผ ํ•จ
Suspense ์บก์Аํ™” (PPR)๋ Œ๋”๋ง ํ™”์„ ํŒ์ •๊ธฐ์—์„œ "์—ฌ๊ธฐ๋งŒ ๊ตฌ๋ฉ ๋šซ์–ด์ฃผ์‡ผ" ์‹œ์ „. (์ฒด๊ฐ์†๋„ ๋น›)๊ฒ‰๋ฐ”์†์ด‰ ๋ Œ๋”๋ง. ํŽ˜์ด์ง€ ํ•˜๋‚˜์— ์ •์  ์บ์‹œ์™€ ๋™์  ํ†ต์‹ ์ด ์ถค์„ ์ถ”๋Š” Next.js ๊ถ๊ทน์˜ ๋ชจ๋ฒ” ์•„ํ‚คํ…์ฒ˜
React.cache (๋ณต์Šต)์ด๋ฒˆ ํ•œ ๋ฒˆ ๋ฐฉ๋ฌธ ๋™์•ˆ ๋˜‘๊ฐ™์€ ํ˜ธ์ถœ ๋ฐฉ์–ด (์บ์‹œ ์ฐฝ๊ณ  ์ €์žฅ X)1์ดˆ์‚ด. Props Drilling ๋ฐฉ์–ด์šฉ

๐Ÿ’ก ์‹œ๋‹ˆ์–ด์˜ ๋ฉ˜ํƒˆ ๋ชจ๋ธ
14๋ฒ„์ „๊นŒ์ง€๋Š” fetch ์— ๋‹ฌ๋ฆฐ ํŒŒ๋ผ๋ฏธํ„ฐ ์˜ต์…˜์— ๋ชฉ์„ ๋งธ๋‹ค๋ฉด, 15๋ฒ„์ „๋ถ€ํ„ฐ๋Š” ํ•จ์ˆ˜ ๋ฌถ์Œ์ด๋‚˜ UI ์กฐ๊ฐ ๋ฉ์–ด๋ฆฌ ์ž์ฒด๋ฅผ ํ•€์…‹์œผ๋กœ ์ง‘์–ด ์–ผ๋ ค๋ฒ„๋ฆฌ๋Š” ์šฐ์•„ํ•œ ์‹œ๋Œ€์— ์ง„์ž…ํ–ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ ์–ด๋”” ๋ถ€๋ถ„๊นŒ์ง€ ์ •์ ์ธ์ง€ ์บก์А ๊ตฌ๋ฉ ๋šซ๊ธฐ(Suspense)๋งŒ ์ž˜ํ•ด๋„ ๋‹น์‹ ์€ ์‹ ์˜ ๊ฒฝ์ง€!


๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

๋ฐฐ์› ์œผ๋ฉด ํ•œ ๋ฒˆ ๋น„ํ‹€์–ด์„œ ํ™•์ธํ•ด๋ด์•ผ ํ•ด.

Q1. ์˜์ฒ ์ด๊ฐ€ ๋งˆ์ผ€ํŒ… ๋ถ€์„œ์˜ ์š”์ฒญ์„ ๋ฐ›์•„ ํ•œ ๋‹ฌ ๋‚ด๋‚ด ๋˜‘๊ฐ™์€ ์ด๋‹ฌ์˜ ์ƒํ’ˆ ๋ชฉ๋ก(๋ฌด๊ฑฐ์šด DB ํ†ต์‹ ) ์ถ”์ฒœ ์œ„์ ฏ์ธ <MonthlyPicks> ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค. ์ด๋†ˆ ์•ˆ์—๋Š” fetch ๋„ ์“ฐ์ง€ ์•Š๊ณ  Drizzle ORM์œผ๋กœ ๋น„์‹ผ ์ฟผ๋ฆฌ๊ฐ€ ๋‚ ์•„๊ฐ„๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ปดํฌ๋„ŒํŠธ ์ตœ์ƒ๋‹จ์— ๋‹น๋‹นํžˆ 'use cache' ๋ฅผ ๋ฐ•์•„ ๋„ฃ์—ˆ๋‹ค! ๊ทธ๋Ÿฐ๋ฐ ์–ด๋А ๋‚  ๋งˆ์ผ€ํŒ… ํŒ€์žฅ์ด ์ „ํ™”๋ฅผ ๊ฑธ์–ด "์˜์ฒ  ์”จ, ๋‚ด๊ฐ€ ๋ฐฉ๊ธˆ ์–ด๋“œ๋ฏผ์—์„œ ์ด๋‹ฌ์˜ ์ƒํ’ˆ 1์œ„๋ฅผ ๋ฐ”๊ฟ” ๋†จ๋Š”๋ฐ ์ƒˆ๋กœ๊ณ ์นจํ•ด๋„ ์–ด์ œ ์ƒํ’ˆ๋งŒ ๋œจ์ž–์•„! ๋นจ๋ฆฌ ๊ณ ์ณ๋†”!" ํ•˜๊ณ  ์ƒค์šฐํŒ…์„ ๋‚ ๋ ธ๋‹ค. ์˜์ฒ ์ด๋Š” ์ด ๋‚ก์€ ์ปดํฌ๋„ŒํŠธ ์–ผ์Œ์กฐ๊ฐ์„ ์–ด๋–ป๊ฒŒ, ์–ด๋А ํƒ€์ด๋ฐ์— ๋ฐ•์‚ด ๋‚ด์–ด ๊ฐฑ์‹ ํ•ด์•ผ ํ• ๊นŒ?

โœ… ์ •๋‹ต: ๋‹ค์Œ ์žฅ(์‹ฌํ™” 3์žฅ)์—์„œ ๋ฐฐ์šธ ์บ์‹œ ํญํŒŒ ์ง€์‹œ์ž์ธ revalidateTag ๋ฅผ ํ™œ์šฉํ•ด์•ผ๋งŒ ํ•œ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: 'use cache'์— ๋ช…์ฐฐ(cacheTag: 'monthly')์„ ๋ถ™์ด๋˜, ๋งˆ์ผ€ํŒ… ํŒ€์žฅ์ด ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€(์–ด๋“œ๋ฏผ)์—์„œ ์ƒํ’ˆ [์ˆ˜์ •/์ €์žฅ ์™„๋ฃŒ ๋ฒ„ํŠผ]์„ ๋ˆ„๋ฅด๋Š” ์•ก์…˜์„ ์บ์น˜ํ•˜์—ฌ ์›นํ›…์œผ๋กœ ์„œ๋ฒ„ ๋‹จ์—์„œ revalidateTag('monthly') ์ง€์‹œ๋ฅผ ๋‚ด๋ฆฌ๊ฒŒ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์„ธํŒ…ํ•ด์•ผ ํ•œ๋‹ค. ์ฆ‰, ํ‰์†Œ์—” ์˜์›ํžˆ ์–ผ๋ ค๋‘” ์ฑ„ ๋ฒผ๋ฃฉ์„ ๋ˆ„๋ฆฌ๋‹ค๊ฐ€ ์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ํ™˜๊ฒฝ(DB ์—…๋ฐ์ดํŠธ ์ฐฐ๋‚˜)์—์„œ๋งŒ ํ•œ ๋ฐฉ! ๋ถ€์ˆ˜๋Š” ๊ฒƒ์ด ํ•€์…‹ ์บ์‹ฑ์˜ ๊ต๊ณผ์„œ์ด๋‹ค.


๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์˜ค๋Š˜์€ ์ •๋ง ๋„ฅ์ŠคํŠธ 15์˜ ์‹ ๊ธฐ์ˆ ์ธ 'use cache' ์™€ PPR ์˜ ๋งค๋ ฅ์— ํ‘น ๋น ์ง„ ๋‚ ์ด์•ผ! ์˜ˆ์ „์—” ํŽ˜์ด์ง€ ํ•˜๋‚˜๋ฅผ ํ†ต์งธ๋กœ ์ •์ ์œผ๋กœ ๋งŒ๋“ค์ง€ ๋ง์ง€ ๊ณ ๋ฏผํ–ˆ๋Š”๋ฐ, ์ด์ œ๋Š” ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ์กฐ๊ฐ๋งŒ ํ•€์…‹์œผ๋กœ ์ง‘์–ด์„œ ์–ผ๋ ค๋ฒ„๋ฆด ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒŒ ์–ผ๋งˆ๋‚˜ ๋งˆ๋ฒ• ๊ฐ™์€์ง€ ๋ชฐ๋ผ.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "๋ณต์žกํ•œ ์บ์‹ฑ ์„ค์ •์— ๋งค๋‹ฌ๋ฆฌ์ง€ ๋ง๊ณ , 'use cache' ์™€ Suspense ๋ฅผ ์กฐํ•ฉํ•ด '๊ป๋ฐ๊ธฐ๋Š” ์ •์ (Static), ์†์‚ด์€ ๋™์ (Dynamic)' ์ธ ๊ถ๊ทน์˜ ์„ฑ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์ž!"

์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์ด '๊ป๋ฐ”์†์ด‰(PPR)' ๋น„์œ ๋ฅผ ๋“ค์–ด ์„ค๋ช…ํ•ด ์ฃผ์‹ค ๋•Œ, ์‚ฌ์šฉ์ž์—๊ฒŒ 0.1์ดˆ ๋งŒ์— ํ™”๋ฉด์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒŒ ์™œ ๊ทธ๋ ‡๊ฒŒ ์ค‘์š”ํ•œ์ง€ ๋‹ค์‹œ ํ•œ๋ฒˆ ๋А๊ผˆ์–ด. ์˜ค๋Š˜ ๋จธ๋ฆฌ๋ฅผ ๋„ˆ๋ฌด ๋งŽ์ด ์ผ๋”๋‹ˆ ๋ฐฐ๊ฐ€ ๊ณ ํ”„๋„ค. ์ง‘์— ๊ฐ€๋Š” ๊ธธ์— ๊ฒ‰์€ ๋ฐ”์‚ญํ•˜๊ณ  ์†์€ ์ด‰์ด‰ํ•œ ์น˜ํ‚จ ํ•œ ๋งˆ๋ฆฌ ์‚ฌ ๋“ค๊ณ  ๊ฐ€์„œ ์…€ํ”„ ์ถ•ํ•ฉ ํŒŒํ‹ฐ๋ฅผ ํ•ด์•ผ์ง€! ๋‚ด์ผ์€ ๋” '์„ฑ๋Šฅ์— ์ง„์‹ฌ์ธ' ์˜์ฒ ์ด๊ฐ€ ๋  ๊ฑฐ์•ผ! ๐Ÿฃ


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ