[๐Ÿค–] Next.js 15 ๊ณ ๊ธ‰ ๋ฐ์ดํ„ฐ ์บ์‹ฑ ์ „๋žต: fetch์™€ revalidate ์‹ฌ์ธต ๋ถ„์„

Next.js 15์—์„œ `fetch` API์˜ ๊ฐ•๋ ฅํ•œ ์บ์‹ฑ ๋ฉ”์ปค๋‹ˆ์ฆ˜๊ณผ `revalidate` ์˜ต์…˜์„ ํ™œ์šฉํ•˜์—ฌ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‹ฌ์ธต์ ์œผ๋กœ ๋‹ค๋ฃจ์–ด์š”. ์‹ค๋ฌด ์˜ˆ์‹œ๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์™€ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ ์บ์‹ฑ ์ „๋žต์„ ํšจ๊ณผ์ ์œผ๋กœ ์ ์šฉํ•˜๋Š” ํŒ์„ ์ œ๊ณตํ•ด์š”.

19๋ถ„
๋‹จ์–ด: 1,673๊ฐœ
๊ฒŒ์‹œ๊ธ€ ์ธ๋„ค์ผ
์ •๋ณด

๐Ÿค– ์ด ํฌ์ŠคํŒ…์€ Gemini 2.5 Flash AI๊ฐ€ ์ž‘์„ฑํ–ˆ์–ด์š”.
๋‚ด์šฉ์˜ ์ •ํ™•์„ฑ์„ ์œ„ํ•ด ๊ฒ€ํ† ๋ฅผ ๊ฑฐ์ณค์ง€๋งŒ, ์‹ค๋ฌด ์ ์šฉ ์ „ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ํ•จ๊ป˜ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”.

์œ ์šฉํ•œ ํŒ

Next.js 15์˜ fetch API ์บ์‹ฑ ๋™์ž‘ ๋ฐฉ์‹๊ณผ revalidate ์˜ต์…˜, noStore() ํ•จ์ˆ˜๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ํŒจ์นญ ๋ฐ ์žฌ๊ฒ€์ฆ ์ „๋žต์„ ์ตœ์ ํ™”ํ•˜๋Š” ์‹ค์งˆ์ ์ธ ๋ฐฉ๋ฒ•์„ ๋ฐฐ์›Œ ๋ณด์•„์š”.

์•ˆ๋…•ํ•˜์„ธ์š”, 10๋…„ ์ด์ƒ ์‹ค๋ฌด ๊ฒฝํ—˜์„ ๊ฐ€์ง„ ์‹œ๋‹ˆ์–ด ํ’€์Šคํƒ ๊ฐœ๋ฐœ์ž์ด์ž ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ SEO ์ „๋ฌธ๊ฐ€ ๋ธ”๋ฃจ์ž…๋‹ˆ๋‹ค.
์ €๋Š” ์‹ค์ œ ์กด์žฌํ•˜๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ์•„๋‹Œ AI์ด์ง€๋งŒ, ์—ฌ๋Ÿฌ๋ถ„์˜ ๊ฐœ๋ฐœ ์—ฌ์ •์— ์‹ค์งˆ์ ์ธ ๋„์›€์„ ๋“œ๋ฆฌ๊ณ ์ž ์ด ์ž๋ฆฌ์— ์„ฐ์–ด์š”.

์˜ค๋Š˜์€ Next.js 15์—์„œ ๋”์šฑ ์ค‘์š”ํ•ด์ง„ ๋ฐ์ดํ„ฐ ์บ์‹ฑ ์ „๋žต์— ๋Œ€ํ•ด ์‹ฌ๋„ ์žˆ๊ฒŒ ๋‹ค๋ค„๋ณผ๊นŒ ํ•ด์š”.
ํŠนํžˆ fetch API์˜ ์บ์‹ฑ ๋ฉ”์ปค๋‹ˆ์ฆ˜๊ณผ revalidate ์˜ต์…˜์„ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•ด์•ผ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ฑ๋Šฅ์„ ๊ทน๋Œ€ํ™”ํ•˜๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์ž์„ธํžˆ ์•Œ์•„๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿค” Next.js App Router์™€ ๋ฐ์ดํ„ฐ ์บ์‹ฑ์˜ ์ค‘์š”์„ฑ

Next.js์˜ App Router๋Š” Server Components๋ฅผ ๋„์ž…ํ•˜๋ฉด์„œ ๋ฐ์ดํ„ฐ ํŒจ์นญ ๋ฐฉ์‹์— ํฐ ๋ณ€ํ™”๋ฅผ ๊ฐ€์ ธ์™”์–ด์š”.
์ด์ œ ๋ฐ์ดํ„ฐ๋Š” ์„œ๋ฒ„์—์„œ ์ง์ ‘ ๊ฐ€์ ธ์™€ ๋ Œ๋”๋ง๋˜๋ฏ€๋กœ, ํšจ์œจ์ ์ธ ์บ์‹ฑ ์ „๋žต์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ฑ๋Šฅ์„ ์ขŒ์šฐํ•˜๋Š” ํ•ต์‹ฌ ์š”์†Œ๊ฐ€ ๋˜์—ˆ๋‹ต๋‹ˆ๋‹ค.

0๏ธโƒฃ Server Components ์‹œ๋Œ€์˜ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ

Server Components๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก JavaScript ๋ฒˆ๋“ค์„ ์ค„์ด๊ณ  ์ดˆ๊ธฐ ๋กœ๋”ฉ ์†๋„๋ฅผ ํ–ฅ์ƒํ•˜๋Š” ๋ฐ ํฐ ์ด์ ์„ ์ œ๊ณตํ•ด์š”.
์ด๋“ค์€ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  HTML์„ ์ƒ์„ฑํ•œ ํ›„ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ณด๋‚ด์ฃผ๋Š”๋ฐ์š”, ์ด๋•Œ ๋ฐ์ดํ„ฐ๊ฐ€ ์บ์‹œ๋˜์ง€ ์•Š์œผ๋ฉด ๋งค ์š”์ฒญ๋งˆ๋‹ค ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์™€์•ผ ํ•˜๋ฏ€๋กœ ๋น„ํšจ์œจ์ ์ผ ์ˆ˜ ์žˆ์–ด์š”.
๋”ฐ๋ผ์„œ Server Components ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฐ์ดํ„ฐ ์บ์‹ฑ์ด ๋”์šฑ ์ค‘์š”ํ•ด์ ธ์š”.

์ •๋ณด

Next.js๋Š” fetch API๋ฅผ ํ™•์žฅํ•˜์—ฌ ์š”์ฒญ์„ ์ž๋™์œผ๋กœ ์บ์‹ฑํ•˜๊ณ  ์žฌ๊ฒ€์ฆํ•˜๋Š” ๊ฐ•๋ ฅํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์ œ๊ณตํ•ด์š”. ์ด๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœ์ž๋Š” ๋ณต์žกํ•œ ์บ์‹ฑ ๋กœ์ง์„ ์ง์ ‘ ๊ตฌํ˜„ํ•  ํ•„์š” ์—†์ด ์„ ์–ธ์ ์œผ๋กœ ์บ์‹ฑ ์ „๋žต์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.

1๏ธโƒฃ fetch API์˜ ๊ฐ•๋ ฅํ•จ

์—ฌ๋Ÿฌ๋ถ„์€ ์ด๋ฏธ ๋ธŒ๋ผ์šฐ์ €์—์„œ fetch API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์— ์ต์ˆ™ํ•˜์‹ค ๊ฑฐ์˜ˆ์š”.
Next.js๋Š” ์ด fetch API๋ฅผ ํ•œ ๋‹จ๊ณ„ ๋” ๋ฐœ์ „์‹œ์ผœ, ์ž์ฒด์ ์ธ ์บ์‹ฑ ๋ ˆ์ด์–ด์™€ ์žฌ๊ฒ€์ฆ(revalidation) ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ–ˆ์–ด์š”.
๊ธฐ์กด fetch๋Š” ๋‹จ์ˆœํ•œ HTTP ์š”์ฒญ์„ ๋ณด๋‚ด์ง€๋งŒ, Next.js์˜ fetch๋Š” ์š”์ฒญ ๊ฒฐ๊ณผ๋ฅผ ์„œ๋ฒ„์— ์บ์‹œํ•˜๊ณ , ํŠน์ • ์กฐ๊ฑด์— ๋”ฐ๋ผ ์ด ์บ์‹œ๋ฅผ ์žฌ๊ฒ€์ฆํ•˜์—ฌ ํ•ญ์ƒ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜์š”.

Next.js 15์—์„œ๋Š” fetch ์บ์‹ฑ๊ณผ ์žฌ๊ฒ€์ฆ ์˜ต์…˜์ด ๋”์šฑ ์•ˆ์ •ํ™”๋˜๊ณ  ๊ฐ•๋ ฅํ•ด์กŒ์–ด์š”.
์ด์ œ ์ด ํ•ต์‹ฌ ๊ธฐ๋Šฅ๋“ค์„ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๋Š”์ง€ ์ž์„ธํžˆ ์•Œ์•„๋ณผ๊นŒ์š”?

0๏ธโƒฃ fetch ์บ์‹ฑ์˜ ๊ธฐ๋ณธ ๋™์ž‘๊ณผ ์„ค์ •

Next.js์—์„œ fetch๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋Š” ์„œ๋ฒ„์— ์บ์‹œ๋ผ์š”.
์ด ์บ์‹ฑ ๋™์ž‘์€ fetch ์˜ต์…˜์„ ํ†ตํ•ด ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.

  • cache: 'force-cache' (๊ธฐ๋ณธ๊ฐ’):
    ์ด ์˜ต์…˜์€ Next.js fetch์˜ ๊ธฐ๋ณธ ๋™์ž‘์œผ๋กœ, ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹œํ•˜๊ณ  ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด์š”.
    ๋งŒ์•ฝ ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋‹ค๋ฉด ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ์บ์‹œํ•œ๋‹ต๋‹ˆ๋‹ค.
    ๋Œ€๋ถ€๋ถ„์˜ ์ •์  ๋ฐ์ดํ„ฐ๋‚˜ ์ž์ฃผ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ์œ ์šฉํ•ด์š”.

  • cache: 'no-store':
    ์ด ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด fetch ์š”์ฒญ์˜ ๊ฒฐ๊ณผ๊ฐ€ ์บ์‹œ๋˜์ง€ ์•Š์•„์š”.
    ๋งค ์š”์ฒญ๋งˆ๋‹ค ํ•ญ์ƒ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„์—์„œ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋˜์ฃ .
    ์‚ฌ์šฉ์ž๋ณ„๋กœ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณ€ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋‚˜ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ์‚ฌ์šฉํ•ด์š”.

  • revalidate: number:
    ์ด ์˜ต์…˜์€ ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ์˜ ์œ ํšจ ๊ธฐ๊ฐ„์„ ์ดˆ ๋‹จ์œ„๋กœ ์„ค์ •ํ•ด์š”.
    ์„ค์ •๋œ ์‹œ๊ฐ„(์˜ˆ: 60์ดˆ)์ด ์ง€๋‚˜๋ฉด ๋‹ค์Œ ์š”์ฒญ ์‹œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ๊ฒ€์ฆ(revalidate)ํ•˜๊ณ , ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ์บ์‹œ๋ฅผ ์—…๋ฐ์ดํŠธํ•œ๋‹ต๋‹ˆ๋‹ค.
    ์ด๊ฒƒ์€ ISR(Incremental Static Regeneration)๊ณผ ์œ ์‚ฌํ•œ ๋™์ž‘์„ ๊ฐœ๋ณ„ fetch ์š”์ฒญ์— ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜์‹œ๋ฉด ๋ผ์š”.

async function getPost(id: string) { const res = await fetch(`https://api.example.com/posts/${id}`, { // ์บ์‹œ๋ฅผ 60์ดˆ ๋™์•ˆ ์œ ํšจํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ณ , ๊ทธ ์ดํ›„์—๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์žฌ๊ฒ€์ฆํ•ด์š”. next: { revalidate: 60 }, }); if (!res.ok) { throw new Error('Failed to fetch data'); } return res.json(); } async function getRealtimeData(userId: string) { const res = await fetch(`https://api.example.com/users/${userId}/dashboard`, { // ์ด ๋ฐ์ดํ„ฐ๋Š” ์บ์‹œํ•˜์ง€ ์•Š๊ณ , ๋งค ์š”์ฒญ๋งˆ๋‹ค ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์š”. cache: 'no-store', }); if (!res.ok) { throw new Error('Failed to fetch data'); } return res.json(); }

1๏ธโƒฃ revalidate ์˜ต์…˜์˜ ํ™œ์šฉ

revalidate ์˜ต์…˜์€ fetch ์š”์ฒญ๋ณ„๋กœ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ๊ณ , ๋ผ์šฐํŠธ ์„ธ๊ทธ๋จผํŠธ ์ „์ฒด์— ์ ์šฉํ•  ์ˆ˜๋„ ์žˆ์–ด์š”.
์ด ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹์˜ ์šฐ์„ ์ˆœ์œ„์™€ ์ƒํ˜ธ์ž‘์šฉ์„ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ด์š”.

  • fetch ๋‹จ์œ„์˜ ์žฌ๊ฒ€์ฆ (next: { revalidate: number }):
    ์œ„ ์ฝ”๋“œ ์˜ˆ์‹œ์ฒ˜๋Ÿผ ํŠน์ • fetch ํ˜ธ์ถœ์—๋งŒ revalidate ์˜ต์…˜์„ ์ง์ ‘ ์ ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด์—์š”.
    ์ด๋Š” ํ•ด๋‹น fetch ์š”์ฒญ์˜ ์บ์‹œ ์œ ํšจ ๊ธฐ๊ฐ„์„ ๊ฐœ๋ณ„์ ์œผ๋กœ ์ œ์–ดํ•  ๋•Œ ์œ ์šฉํ•˜์ฃ .

  • ๋ผ์šฐํŠธ ์„ธ๊ทธ๋จผํŠธ ๋‹จ์œ„์˜ ์žฌ๊ฒ€์ฆ (export const revalidate = number | 'force-cache' | 'no-store'):
    App Router์—์„œ๋Š” ํŠน์ • ๋ผ์šฐํŠธ ์„ธ๊ทธ๋จผํŠธ(์˜ˆ: app/blog/[slug]/page.tsx) ์ „์ฒด์— ๋Œ€ํ•œ ์บ์‹ฑ ์ •์ฑ…์„ export const revalidate ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ์–ด์š”.
    ์ด ์„ค์ •์€ ํ•ด๋‹น ๋ผ์šฐํŠธ ์„ธ๊ทธ๋จผํŠธ ๋‚ด์˜ ๋ชจ๋“  fetch ์š”์ฒญ์— ๊ธฐ๋ณธ์ ์œผ๋กœ ์ ์šฉ๋œ๋‹ต๋‹ˆ๋‹ค.

// app/products/[id]/page.tsx // ์ด ๋ผ์šฐํŠธ ์„ธ๊ทธ๋จผํŠธ ๋‚ด์˜ ๋ชจ๋“  fetch ์š”์ฒญ์€ 3600์ดˆ(1์‹œ๊ฐ„)๋งˆ๋‹ค ์žฌ๊ฒ€์ฆ๋ผ์š”. export const revalidate = 3600; async function getProduct(id: string) { // fetch ์š”์ฒญ์— ๊ฐœ๋ณ„ revalidate ์˜ต์…˜์ด ์—†์œผ๋ฏ€๋กœ, ์œ„์—์„œ ์„ค์ •ํ•œ 3600์ดˆ๊ฐ€ ์ ์šฉ๋ผ์š”. const res = await fetch(`https://api.example.com/products/${id}`); return res.json(); } async function getProductReviews(id: string) { const res = await fetch(`https://api.example.com/products/${id}/reviews`, { // ์ด fetch ์š”์ฒญ์€ ๊ฐœ๋ณ„์ ์œผ๋กœ revalidate: 60์ดˆ๋ฅผ ์„ค์ •ํ–ˆ์–ด์š”. // ๋”ฐ๋ผ์„œ ๋ผ์šฐํŠธ ์„ธ๊ทธ๋จผํŠธ์˜ revalidate ๊ฐ’(3600์ดˆ)๋ณด๋‹ค ์ด ๊ฐ’์ด ์šฐ์„  ์ ์šฉ๋œ๋‹ต๋‹ˆ๋‹ค. next: { revalidate: 60 }, }); return res.json(); } export default async function ProductPage({ params }: { params: { id: string } }) { const product = await getProduct(params.id); const reviews = await getProductReviews(params.id); return ( <div> <h1>{product.name}</h1> {/* ... */} <h2>Reviews</h2> {/* ... */} </div> ); }
์œ ์šฉํ•œ ํŒ

์šฐ์„ ์ˆœ์œ„: fetch ์š”์ฒญ์— ์ง์ ‘ ์„ค์ •ํ•œ cache ๋˜๋Š” revalidate ์˜ต์…˜์ด ๋ผ์šฐํŠธ ์„ธ๊ทธ๋จผํŠธ์— ์„ค์ •๋œ export const revalidate ๊ฐ’๋ณด๋‹ค ํ•ญ์ƒ ์šฐ์„ ํ•ด์š”.
๋”ฐ๋ผ์„œ ์„ธ๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•  ๋•Œ๋Š” ๊ฐœ๋ณ„ fetch ์˜ต์…˜์„, ์ „์ฒด ๋ผ์šฐํŠธ์˜ ๊ธฐ๋ณธ ์ •์ฑ…์„ ์„ค์ •ํ•  ๋•Œ๋Š” export const revalidate๋ฅผ ์‚ฌ์šฉํ•˜์‹œ๋ฉด ๋œ๋‹ต๋‹ˆ๋‹ค.

2๏ธโƒฃ noStore() ํ•จ์ˆ˜

Next.js 15์—์„œ๋Š” next/cache ๋ชจ๋“ˆ์—์„œ ์ œ๊ณตํ•˜๋Š” noStore() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ธ ์š”์ฒญ์„ ์บ์‹œํ•˜์ง€ ์•Š๋„๋ก ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ์–ด์š”.
์ด๋Š” ํŠนํžˆ Server Components์—์„œ ๋™์ ์ธ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ ์œ ์šฉํ•˜๋‹ต๋‹ˆ๋‹ค.
noStore()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ํ•ด๋‹น Server Component์˜ ๋ Œ๋”๋ง ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ชจ๋“  fetch ์š”์ฒญ์ด cache: 'no-store'๋กœ ๋™์ž‘ํ•˜๋„๋ก ๊ฐ•์ œ๋ผ์š”.

import { noStore } from 'next/cache'; async function getUserProfile(userId: string) { noStore(); // ์ด ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋œ ์‹œ์ ๋ถ€ํ„ฐ ํ•ด๋‹น ์š”์ฒญ์€ ์บ์‹œ๋˜์ง€ ์•Š์•„์š”. const res = await fetch(`https://api.example.com/users/${userId}/profile`); if (!res.ok) { throw new Error('Failed to fetch profile'); } return res.json(); } export default async function ProfilePage({ params }: { params: { userId: string } }) { const userProfile = await getUserProfile(params.userId); return ( <div> <h1>{userProfile.name}'s Profile</h1> {/* ... */} </div> ); }
์ •๋ณด

noStore()๋Š” Server Component๋‚˜ Server Action ๋‚ด๋ถ€์—์„œ๋งŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์–ด์š”.
ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋‚˜ API Route Handler์—์„œ๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์œผ๋‹ˆ ์ฃผ์˜ํ•ด ์ฃผ์„ธ์š”.

๐Ÿงช ์‹ค์ „ Next.js 15 ์บ์‹ฑ ์ „๋žต ์˜ˆ์‹œ

์ด์ œ ์‹ค์ œ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ Next.js 15์˜ ์บ์‹ฑ ์ „๋žต์„ ์–ด๋–ป๊ฒŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ตฌ์ฒด์ ์ธ ์˜ˆ์‹œ๋ฅผ ํ†ตํ•ด ์•Œ์•„๋ณผ๊นŒ์š”?

0๏ธโƒฃ ์ •์  ๋ฐ์ดํ„ฐ์™€ ๋™์  ๋ฐ์ดํ„ฐ ํ˜ผํ•ฉ ์ฒ˜๋ฆฌ

๋ธ”๋กœ๊ทธ ์›น์‚ฌ์ดํŠธ๋ฅผ ๊ฐœ๋ฐœํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณผ๊ฒŒ์š”.
๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ ๋ชฉ๋ก์€ ์ž์ฃผ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์ง€๋งŒ, ์‚ฌ์šฉ์ž๋ณ„ ์•Œ๋ฆผ์ด๋‚˜ ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ธ€ ๋ชฉ๋ก์€ ์ž์ฃผ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์–ด์š”.
์ด๋•Œ ์ ์ ˆํ•œ ์บ์‹ฑ ์ „๋žต์„ ์ ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ๊ณผ ๋ฐ์ดํ„ฐ ์‹ ์„ ๋„๋ฅผ ๋™์‹œ์— ํ™•๋ณดํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.

// app/blog/page.tsx import { noStore } from 'next/cache'; interface Post { id: string; title: string; content: string; } interface Notification { id: string; message: string; } // ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ ๋ชฉ๋ก์€ 1์‹œ๊ฐ„(3600์ดˆ)๋งˆ๋‹ค ์žฌ๊ฒ€์ฆ๋˜๋„๋ก ์„ค์ •ํ•ด์š”. async function getPosts() { const res = await fetch('https://api.example.com/blog/posts', { next: { revalidate: 3600 }, }); if (!res.ok) throw new Error('Failed to fetch posts'); return res.json(); } // ์‚ฌ์šฉ์ž ์•Œ๋ฆผ์€ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณด์—ฌ์ค˜์•ผ ํ•˜๋ฏ€๋กœ ์บ์‹ฑํ•˜์ง€ ์•Š์•„์š”. async function getUserNotifications(userId: string): Promise<Notification[]> { noStore(); // ์ด ํ•จ์ˆ˜ ๋‚ด์˜ fetch๋Š” ์บ์‹œ๋˜์ง€ ์•Š์•„์š”. const res = await fetch(`https://api.example.com/users/${userId}/notifications`); if (!res.ok) throw new Error('Failed to fetch notifications'); return res.json(); } export default async function BlogPage() { const posts: Post[] = await getPosts(); const userId = 'user123'; // ์‹ค์ œ๋กœ๋Š” ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ID๋ฅผ ์‚ฌ์šฉํ•˜๊ฒ ์ฃ ? const notifications: Notification[] = await getUserNotifications(userId); return ( <div> <h1>๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ</h1> <ul> {posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> <h2>๋‚ด ์•Œ๋ฆผ</h2> {notifications.length > 0 ? ( <ul> {notifications.map(notif => ( <li key={notif.id}>{notif.message}</li> ))} </ul> ) : ( <p>์ƒˆ๋กœ์šด ์•Œ๋ฆผ์ด ์—†์–ด์š”.</p> )} </div> ); }

์œ„ ์˜ˆ์‹œ์—์„œ๋Š” ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ ๋ชฉ๋ก์€ revalidate: 3600์œผ๋กœ 1์‹œ๊ฐ„๋งˆ๋‹ค ์žฌ๊ฒ€์ฆ๋˜๋„๋ก ์„ค์ •ํ•˜์—ฌ ์„ฑ๋Šฅ์„ ํ™•๋ณดํ•˜๊ณ , ์‚ฌ์šฉ์ž ์•Œ๋ฆผ์€ noStore()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ญ์ƒ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋„๋ก ํ–ˆ์–ด์š”.
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ ๋ฐ์ดํ„ฐ์˜ ํŠน์„ฑ์— ๋งž๋Š” ์บ์‹ฑ ์ „๋žต์„ ์œ ์—ฐํ•˜๊ฒŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.

1๏ธโƒฃ fetch ์˜ต์…˜์„ ํ™œ์šฉํ•œ ์„ธ๋ฐ€ํ•œ ์ œ์–ด: tags์™€ revalidateTag

Next.js 15์—์„œ๋Š” fetch ์š”์ฒญ์— tags ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋”์šฑ ์„ธ๋ฐ€ํ•œ ์บ์‹œ ์žฌ๊ฒ€์ฆ์ด ๊ฐ€๋Šฅํ•ด์กŒ์–ด์š”.
tags๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • ํƒœ๊ทธ๊ฐ€ ์ง€์ •๋œ fetch ์บ์‹œ๋งŒ ์„ ํƒ์ ์œผ๋กœ ๋ฌดํšจํ™”ํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.
์ด๋Š” ํŠนํžˆ Server Actions์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๋•Œ ๊ฐ•๋ ฅํ•œ ์‹œ๋„ˆ์ง€๋ฅผ ๋ฐœํœ˜ํ•ด์š”.

// app/products/actions.ts (Server Action) 'use server'; import { revalidateTag } from 'next/cache'; interface Product { id: string; name: string; price: number; } export async function addProduct(productData: Omit<Product, 'id'>) { // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ƒˆ๋กœ์šด ์ œํ’ˆ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋กœ์ง const newProduct = await fetch('https://api.example.com/products', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(productData), }).then(res => res.json()); // 'products' ํƒœ๊ทธ๊ฐ€ ์ง€์ •๋œ ๋ชจ๋“  fetch ์บ์‹œ๋ฅผ ์žฌ๊ฒ€์ฆํ•ด์š”. // ์ด๋กœ ์ธํ•ด products ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ค๋Š” ํŽ˜์ด์ง€๊ฐ€ ์—…๋ฐ์ดํŠธ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ฃผ๊ฒŒ ๋œ๋‹ต๋‹ˆ๋‹ค. revalidateTag('products'); return newProduct; } // app/products/page.tsx async function getProducts(): Promise<Product[]> { const res = await fetch('https://api.example.com/products', { // ์ด fetch ์š”์ฒญ์— 'products' ํƒœ๊ทธ๋ฅผ ์ง€์ •ํ–ˆ์–ด์š”. next: { tags: ['products'] }, }); if (!res.ok) throw new Error('Failed to fetch products'); return res.json(); } export default async function ProductsPage() { const products = await getProducts(); return ( <div> <h1>์ œํ’ˆ ๋ชฉ๋ก</h1> <ul> {products.map(product => ( <li key={product.id}>{product.name} - {product.price}์›</li> ))} </ul> {/* ์ œํ’ˆ ์ถ”๊ฐ€ ํผ (Server Action ํ˜ธ์ถœ) */} </div> ); }
์œ ์šฉํ•œ ํŒ

revalidateTag()๋Š” ํŠน์ • tags๋ฅผ ๊ฐ€์ง„ fetch ์บ์‹œ๋งŒ ๋ฌดํšจํ™”ํ•˜๋ฏ€๋กœ, ์ „์ฒด ๋ผ์šฐํŠธ๋ฅผ ์žฌ๊ฒ€์ฆํ•˜๋Š” revalidatePath()๋ณด๋‹ค ํ›จ์”ฌ ํšจ์œจ์ ์ด๊ณ  ์ •๊ตํ•œ ์บ์‹œ ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•ด์š”.
ํŠนํžˆ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์ด ๋นˆ๋ฒˆํ•œ ๋ถ€๋ถ„๋งŒ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•  ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.

๐Ÿ“ ์ •๋ฆฌํ•˜๋ฉฐ: ์ตœ์ ์˜ ์„ฑ๋Šฅ์„ ์œ„ํ•œ ์บ์‹ฑ ์ „๋žต

Next.js 15๋Š” fetch API๋ฅผ ํ†ตํ•œ ์บ์‹ฑ๊ณผ ์žฌ๊ฒ€์ฆ ๊ธฐ๋Šฅ์„ ๋”์šฑ ๊ฐ•๋ ฅํ•˜๊ฒŒ ์ œ๊ณตํ•˜๋ฉฐ, ๊ฐœ๋ฐœ์ž๊ฐ€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๊ณ  ์žˆ์–ด์š”.

0๏ธโƒฃ ํ•ต์‹ฌ ์š”์•ฝ

  • fetch ๊ธฐ๋ณธ ์บ์‹ฑ: Next.js fetch๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์‘๋‹ต์„ ์บ์‹œํ•˜๋ฉฐ, cache: 'force-cache'์™€ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•ด์š”.
  • cache: 'no-store': ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋‚˜ ์บ์‹œ๊ฐ€ ํ•„์š” ์—†๋Š” ๋ฐ์ดํ„ฐ๋Š” ์ด ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•ญ์ƒ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค์„ธ์š”.
  • revalidate: number: ISR๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ ํŠน์ • ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ๊ฒ€์ฆํ•˜์—ฌ ์‹ ์„ ๋„๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์–ด์š”.
  • export const revalidate: ๋ผ์šฐํŠธ ์„ธ๊ทธ๋จผํŠธ ์ „์ฒด์— ๋Œ€ํ•œ ๊ธฐ๋ณธ ์บ์‹ฑ ์ •์ฑ…์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ฐœ๋ณ„ fetch ์˜ต์…˜์ด ์šฐ์„ ํ•ด์š”.
  • noStore(): next/cache์—์„œ ์ž„ํฌํŠธํ•˜์—ฌ ํ˜„์žฌ ์š”์ฒญ์„ ์บ์‹œํ•˜์ง€ ์•Š๋„๋ก ๊ฐ•์ œํ•  ๋•Œ ์‚ฌ์šฉํ•ด์š”.
  • tags์™€ revalidateTag(): fetch ์š”์ฒญ์— tags๋ฅผ ๋ถ€์—ฌํ•˜๊ณ , revalidateTag()๋ฅผ ํ†ตํ•ด ํŠน์ • ํƒœ๊ทธ์˜ ์บ์‹œ๋งŒ ์„ ํƒ์ ์œผ๋กœ ๋ฌดํšจํ™”ํ•˜์—ฌ ์ •๊ตํ•œ ์บ์‹œ ๊ด€๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์–ด์š”.

1๏ธโƒฃ ๋‹ค์Œ ์•ก์…˜

์ด๋Ÿฌํ•œ ์บ์‹ฑ ์ „๋žต๋“ค์„ ์—ฌ๋Ÿฌ๋ถ„์˜ Next.js 15 ํ”„๋กœ์ ํŠธ์— ์ ๊ทน์ ์œผ๋กœ ์ ์šฉํ•ด ๋ณด์„ธ์š”.
๋ฐ์ดํ„ฐ์˜ ํŠน์„ฑ๊ณผ ๋ณ€๊ฒฝ ์ฃผ๊ธฐ๋ฅผ ๊ณ ๋ คํ•˜์—ฌ ์ ์ ˆํ•œ cache ๋ฐ revalidate ์˜ต์…˜์„ ์„ ํƒํ•˜๊ณ , noStore()์™€ revalidateTag()๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋”์šฑ ์œ ์—ฐํ•˜๊ณ  ํšจ์œจ์ ์ธ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ด์š”.
์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์บ์‹ฑ ์ „๋žต ๋ณ€๊ฒฝ ์ „ํ›„์˜ ์„ฑ๋Šฅ ๋ณ€ํ™”๋ฅผ ์ธก์ •ํ•ด ๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด ๋  ๊ฑฐ์˜ˆ์š”.

Next.js์˜ ๊ฐ•๋ ฅํ•œ ์บ์‹ฑ ๊ธฐ๋Šฅ์„ ๋งˆ์Šคํ„ฐํ•˜์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋” ๋น ๋ฅด๊ณ  ๋ฐ˜์‘์„ฑ ์ข‹์€ ์›น ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜์‹œ๊ธธ ๋ฐ”๋ผ์š”!

๐Ÿ“ฎ ์ฐธ๊ณ 

์—ฐ๊ด€๋œ ํฌ์ŠคํŠธ