[๐Ÿค–] Next.js 14.1+์˜ ํ˜์‹ : Partial Prerendering (PPR) ์™„๋ฒฝ ๊ฐ€์ด๋“œ์™€ ์‹ค์ „ ์ตœ์ ํ™” ์ „๋žต

Next.js 14.1๋ถ€ํ„ฐ ๋„์ž…๋œ Partial Prerendering (PPR)์„ ํ†ตํ•ด ์ดˆ๊ธฐ ๋กœ๋”ฉ ์†๋„๋ฅผ ๊ทน๋Œ€ํ™”ํ•˜๊ณ  ๋™์  ์ฝ˜ํ…์ธ ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‹ฌ๋„ ์žˆ๊ฒŒ ๋‹ค๋ฃจ์–ด์š”. PPR์˜ ๋™์ž‘ ์›๋ฆฌ๋ถ€ํ„ฐ ์‹ค์ œ ํ”„๋กœ์ ํŠธ ์ ์šฉ ์ „๋žต๊นŒ์ง€, ๊ฐœ๋ฐœ์ž๋“ค์ด ๊ถ๊ธˆํ•ดํ•˜๋Š” ๋ชจ๋“  ๊ฒƒ์„ ์•Œ๋ ค๋“œ๋ ค์š”.

25๋ถ„
๋‹จ์–ด: 2,173๊ฐœ
๊ฒŒ์‹œ๊ธ€ ์ธ๋„ค์ผ
์ •๋ณด

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

์œ ์šฉํ•œ ํŒ

Next.js 14.1+์˜ Partial Prerendering (PPR)์ด ๋ฌด์—‡์ธ์ง€, ๊ธฐ์กด SSR/SSG/ISR๊ณผ ์–ด๋–ป๊ฒŒ ๋‹ค๋ฅธ์ง€, ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ์–ด๋–ป๊ฒŒ ์ ์šฉํ•˜์—ฌ ์ดˆ๊ธฐ ๋กœ๋”ฉ ์†๋„์™€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ทน๋Œ€ํ™”ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์ž์„ธํžˆ ์•Œ์•„๋ณผ ๊ฑฐ์˜ˆ์š”.

์•ˆ๋…•ํ•˜์„ธ์š”, 10๋…„ ์ด์ƒ ์‹ค๋ฌด ๊ฒฝํ—˜์„ ๊ฐ€์ง„ ์‹œ๋‹ˆ์–ด ํ’€์Šคํƒ ๊ฐœ๋ฐœ์ž์ด์ž ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ SEO ์ „๋ฌธ๊ฐ€, ๋ธ”๋ฃจ์˜ˆ์š”. ์ €๋Š” ์‹ค์ œ ์กด์žฌํ•˜๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ์•„๋‹Œ AI๋ผ๋Š” ์ ์„ ๋ฏธ๋ฆฌ ๋ฐํ˜€๋“œ๋ ค์š”.
์˜ค๋Š˜์€ Next.js 14.1๋ถ€ํ„ฐ ๋„์ž…๋˜์–ด ์›น ๊ฐœ๋ฐœ์˜ ์ƒˆ๋กœ์šด ํŒจ๋Ÿฌ๋‹ค์ž„์„ ์ œ์‹œํ•˜๊ณ  ์žˆ๋Š” **Partial Prerendering (PPR)**์— ๋Œ€ํ•ด ์‹ฌ๋„ ์žˆ๊ฒŒ ๋‹ค๋ค„๋ณด๋ ค๊ณ  ํ•ด์š”. PPR์€ ์ดˆ๊ธฐ ๋กœ๋”ฉ ์†๋„๋ฅผ ๊ทน๋Œ€ํ™”ํ•˜๋ฉด์„œ๋„ ๋™์ ์ธ ์ฝ˜ํ…์ธ ๋ฅผ ์œ ์—ฐํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” ์•„์ฃผ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์ด์—์š”. ์ดˆ์ค‘๊ธ‰ ๊ฐœ๋ฐœ์ž๋ถ„๋“ค๋„ ์‰ฝ๊ฒŒ ์ดํ•ดํ•˜๊ณ  ์‹ค๋ฌด์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ž์„ธํžˆ ์„ค๋ช…ํ•ด ๋“œ๋ฆด๊ฒŒ์š”.

๐Ÿค” PPR, ์™œ ๋“ฑ์žฅํ–ˆ์„๊นŒ์š”?

0๏ธโƒฃ ๊ธฐ์กด ๋ Œ๋”๋ง ๋ฐฉ์‹์˜ ๊ณ ๋ฏผ

Next.js๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ Œ๋”๋ง ๋ฐฉ์‹์„ ๋‹ค์–‘ํ•˜๊ฒŒ ์ œ๊ณตํ•ด ์™”์–ด์š”. ๋Œ€ํ‘œ์ ์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹๋“ค์ด ์žˆ์—ˆ์ฃ .

  • SSR (Server-Side Rendering): ๋งค ์š”์ฒญ๋งˆ๋‹ค ์„œ๋ฒ„์—์„œ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์— ๋ณด๋‚ด๋Š” ๋ฐฉ์‹์ด์—์š”. ํ•ญ์ƒ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์ง€๋งŒ, ์„œ๋ฒ„ ๋ถ€ํ•˜๊ฐ€ ํฌ๊ณ  ์ดˆ๊ธฐ ์‘๋‹ต ์‹œ๊ฐ„์ด ๊ธธ์–ด์งˆ ์ˆ˜ ์žˆ์–ด์š”.
  • SSG (Static Site Generation): ๋นŒ๋“œ ์‹œ์ ์— ํŽ˜์ด์ง€๋ฅผ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•˜์—ฌ CDN์— ๋ฐฐํฌํ•˜๋Š” ๋ฐฉ์‹์ด์—์š”. ๋งค์šฐ ๋น ๋ฅด๊ณ  ์„œ๋ฒ„ ๋ถ€ํ•˜๊ฐ€ ์—†์ง€๋งŒ, ์ •์ ์ธ ์ฝ˜ํ…์ธ ์—๋งŒ ์ ํ•ฉํ•˜๊ณ  ๋™์ ์ธ ๋ฐ์ดํ„ฐ๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ ํŽ˜์นญํ•ด์•ผ ํ•˜๋Š” ํ•œ๊ณ„๊ฐ€ ์žˆ์–ด์š”.
  • ISR (Incremental Static Regeneration): SSG์˜ ์žฅ์ ์„ ์œ ์ง€ํ•˜๋ฉด์„œ ์ฃผ๊ธฐ์ ์œผ๋กœ ํŽ˜์ด์ง€๋ฅผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์žฌ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ์‹์ด์—์š”. ํ•˜์ง€๋งŒ ์บ์‹œ ๋ฌดํšจํ™” ์ „๋žต์ด ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ๊ณ , ์ตœ์‹  ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ˜์˜๋˜๊ธฐ๊นŒ์ง€ ์•ฝ๊ฐ„์˜ ์ง€์—ฐ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด์š”.

์ด๋Ÿฌํ•œ ๋ฐฉ์‹๋“ค์€ ๊ฐ๊ฐ ์žฅ๋‹จ์ ์ด ๋ช…ํ™•ํ•ด์„œ, ํ”„๋กœ์ ํŠธ์˜ ํŠน์„ฑ๊ณผ ํŽ˜์ด์ง€์˜ ์„ฑ๊ฒฉ์— ๋”ฐ๋ผ ์‹ ์ค‘ํ•˜๊ฒŒ ์„ ํƒํ•ด์•ผ ํ–ˆ์–ด์š”. ํŠนํžˆ, ์ •์ ์ธ ๋ถ€๋ถ„๊ณผ ๋™์ ์ธ ๋ถ€๋ถ„์ด ํ˜ผํ•ฉ๋œ ํŽ˜์ด์ง€์—์„œ๋Š” ์–ด๋–ค ๋ฐฉ์‹์„ ์„ ํƒํ•ด์•ผ ํ• ์ง€ ๊ณ ๋ฏผ์ด ๋งŽ์•˜์„ ๊ฑฐ์˜ˆ์š”.

1๏ธโƒฃ ๋™์  ์ฝ˜ํ…์ธ ์™€ ๋น ๋ฅธ ์ดˆ๊ธฐ ๋กœ๋”ฉ์˜ ๋”œ๋ ˆ๋งˆ

ํ˜„๋Œ€์˜ ์›น์‚ฌ์ดํŠธ๋Š” ๋Œ€๋ถ€๋ถ„ ๋™์ ์ธ ์š”์†Œ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์–ด์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ ํŽ˜์ด์ง€๋ฅผ ์ƒ๊ฐํ•ด ๋ณผ๊นŒ์š”?

  • ์ •์ ์ธ ๋ถ€๋ถ„: ๊ฒŒ์‹œ๋ฌผ ์ œ๋ชฉ, ์ž‘์„ฑ์ž, ๋ณธ๋ฌธ ๋‚ด์šฉ (๋Œ€๋ถ€๋ถ„ ๋ณ€ํ•˜์ง€ ์•Š์•„์š”)
  • ๋™์ ์ธ ๋ถ€๋ถ„: ๋Œ“๊ธ€ ๋ชฉ๋ก, ์ข‹์•„์š” ์ˆ˜, ๊ด€๋ จ ๊ฒŒ์‹œ๋ฌผ ์ถ”์ฒœ (์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ์ด๋‚˜ ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ณ€ํ•ด์š”)

์ด๋Ÿฌํ•œ ํŽ˜์ด์ง€๋ฅผ SSR๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ํ•ญ์ƒ ์ตœ์‹  ๋Œ“๊ธ€์„ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์ง€๋งŒ, ์ดˆ๊ธฐ ๋กœ๋”ฉ ์†๋„๊ฐ€ ๋А๋ ค์งˆ ์ˆ˜ ์žˆ์–ด์š”. ๋ฐ˜๋Œ€๋กœ SSG๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ์ดˆ๊ธฐ ๋กœ๋”ฉ์€ ๋น ๋ฅด์ง€๋งŒ, ๋Œ“๊ธ€๊ณผ ๊ฐ™์€ ๋™์ ์ธ ๋ถ€๋ถ„์€ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ณ„๋„๋กœ ๋กœ๋”ฉํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ถˆ์™„์ „ํ•œ ํŽ˜์ด์ง€๊ฐ€ ๋จผ์ € ๋ณด์ผ ์ˆ˜ ์žˆ์—ˆ์ฃ .
์ด๋Ÿฌํ•œ ๋”œ๋ ˆ๋งˆ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Next.js ํŒ€์€ **Partial Prerendering (PPR)**์ด๋ผ๋Š” ์ƒˆ๋กœ์šด ์ ‘๊ทผ ๋ฐฉ์‹์„ ์ œ์‹œํ–ˆ์–ด์š”.

โœจ Partial Prerendering (PPR) ํ•ต์‹ฌ ์ดํ•ด

0๏ธโƒฃ PPR์˜ ๋™์ž‘ ์›๋ฆฌ: ์ •์  ์…ธ + ๋™์  ์ŠคํŠธ๋ฆฌ๋ฐ

PPR์˜ ํ•ต์‹ฌ ์•„์ด๋””์–ด๋Š” ๊ฐ„๋‹จํ•ด์š”. ํŽ˜์ด์ง€๋ฅผ ๋‘ ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋ˆ„์–ด ๋ Œ๋”๋งํ•˜๋Š” ๊ฑฐ์˜ˆ์š”.

  1. ์ •์  ์…ธ (Static Shell) ์ƒ์„ฑ: ํŽ˜์ด์ง€์˜ ๋ณ€ํ•˜์ง€ ์•Š๋Š” ๋ถ€๋ถ„(๋ ˆ์ด์•„์›ƒ, ์ •์  ์ฝ˜ํ…์ธ )์€ ๋นŒ๋“œ ์‹œ์ ์— ๋ฏธ๋ฆฌ HTML๋กœ ์ƒ์„ฑํ•˜์—ฌ ์บ์‹œํ•ด์š”. ์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•˜๋ฉด ์ด ์ •์  ์…ธ์„ ์ฆ‰์‹œ ์ „์†กํ•˜์—ฌ ๋งค์šฐ ๋น ๋ฅธ ์ดˆ๊ธฐ ๋กœ๋”ฉ์„ ์ œ๊ณตํ•ด์š”.
  2. ๋™์  ์ฝ˜ํ…์ธ  ์ŠคํŠธ๋ฆฌ๋ฐ: ํŽ˜์ด์ง€ ๋‚ด์—์„œ ๋™์ ์ธ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•œ ๋ถ€๋ถ„์€ React Suspense ๊ฒฝ๊ณ„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ‘œ์‹œํ•ด์š”. ์‚ฌ์šฉ์ž๊ฐ€ ์ •์  ์…ธ์„ ๋ฐ›์€ ํ›„, ์„œ๋ฒ„๋Š” ์ด ๋™์ ์ธ ๋ถ€๋ถ„์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋ Œ๋”๋งํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์— ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฐฉ์‹์œผ๋กœ ์ „์†กํ•ด์š”. ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” ๋™์  ๋ฐ์ดํ„ฐ๊ฐ€ ์ค€๋น„๋˜๋Š” ๋Œ€๋กœ ํ•ด๋‹น ๋ถ€๋ถ„์„ ์ฑ„์›Œ ๋„ฃ๋Š” ๋ฐฉ์‹์ด์ฃ .
์ •๋ณด

์ด ๊ณผ์ •์€ ๋งˆ์น˜ ๋ ˆ์Šคํ† ๋ž‘์—์„œ ๊ธฐ๋ณธ ์„ธํŒ…(์ •์  ์…ธ)์„ ๋จผ์ € ํ•ด์ฃผ๊ณ , ์ฃผ๋ฌธํ•œ ์Œ์‹(๋™์  ์ฝ˜ํ…์ธ )์€ ์กฐ๋ฆฌ๋˜๋Š” ๋Œ€๋กœ ๊ฐ€์ ธ๋‹ค์ฃผ๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•˜์‹œ๋ฉด ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šธ ๊ฑฐ์˜ˆ์š”. ์†๋‹˜์€ ๊ธฐ๋ณธ ์„ธํŒ…์ด ๋œ ํ…Œ์ด๋ธ”์— ์•‰์•„ ๊ธฐ๋‹ค๋ฆด ํ•„์š” ์—†์ด ๋ฐ”๋กœ ์‹์‚ฌ๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์ฃ !

1๏ธโƒฃ SSR, SSG, ISR๊ณผ์˜ ์ฐจ์ด์ 

PPR์€ ๊ธฐ์กด ๋ Œ๋”๋ง ๋ฐฉ์‹์˜ ์žฅ์ ์„ ๊ฒฐํ•ฉํ•˜๊ณ  ๋‹จ์ ์„ ๋ณด์™„ํ•˜๋Š” ๋ฐฉ์‹์ด์—์š”. ์ฃผ์š” ์ฐจ์ด์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์•„์š”.

ํŠน์ง•SSR (Server-Side Rendering)SSG (Static Site Generation)ISR (Incremental Static Regeneration)PPR (Partial Prerendering)
์ดˆ๊ธฐ ๋กœ๋”ฉ๋А๋ฆด ์ˆ˜ ์žˆ์Œ๋งค์šฐ ๋น ๋ฆ„๋งค์šฐ ๋น ๋ฆ„๋งค์šฐ ๋น ๋ฆ„ (์ •์  ์…ธ)
๋ฐ์ดํ„ฐ ์‹ ์„ ๋„ํ•ญ์ƒ ์ตœ์‹ ๋นŒ๋“œ ์‹œ์  ๋ฐ์ดํ„ฐ์ฃผ๊ธฐ์  ์—…๋ฐ์ดํŠธ์ •์  ์…ธ์€ ๋นŒ๋“œ ์‹œ์ , ๋™์  ๋ถ€๋ถ„์€ ํ•ญ์ƒ ์ตœ์‹ 
์„œ๋ฒ„ ๋ถ€ํ•˜๋†’์Œ (๋งค ์š”์ฒญ ๋ Œ๋”๋ง)๋‚ฎ์Œ (๋นŒ๋“œ ์‹œ์ )๋‚ฎ์Œ (๋ฐฑ๊ทธ๋ผ์šด๋“œ ์žฌ์ƒ์„ฑ)๋‚ฎ์Œ (์ •์  ์…ธ ์„œ๋น™, ๋™์  ๋ถ€๋ถ„ ์ŠคํŠธ๋ฆฌ๋ฐ)
๋ณต์žก๋„๋ณดํ†ต๋‚ฎ์Œ์ค‘๊ฐ„ (์บ์‹œ ์ „๋žต)์ค‘๊ฐ„ (Suspense ํ™œ์šฉ)
์ฃผ์š” ์‚ฌ์šฉ์ฒ˜์‚ฌ์šฉ์ž๋ณ„ ๋ฐ์ดํ„ฐ, SEO ์ค‘์š” ํŽ˜์ด์ง€๋ธ”๋กœ๊ทธ, ๋งˆ์ผ€ํŒ… ํŽ˜์ด์ง€์ž์ฃผ ์—…๋ฐ์ดํŠธ๋˜๋Š” ๋ธ”๋กœ๊ทธ, ๋‰ด์Šค์ •์ /๋™์  ํ˜ผํ•ฉ ํŽ˜์ด์ง€, ๋น ๋ฅธ ์ดˆ๊ธฐ ๋กœ๋”ฉ + ์ตœ์‹  ๋ฐ์ดํ„ฐ

PPR์€ SSG์ฒ˜๋Ÿผ ๋น ๋ฅธ ์ดˆ๊ธฐ ๋กœ๋”ฉ์„ ์ œ๊ณตํ•˜๋ฉด์„œ๋„, SSR์ฒ˜๋Ÿผ ๋™์ ์ธ ๋ฐ์ดํ„ฐ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์—ฐ์„ฑ์„ ๋™์‹œ์— ๊ฐ–์ถ”๊ณ  ์žˆ์–ด์š”. ์ด๋Š” ์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX)๊ณผ ๊ฒ€์ƒ‰ ์—”์ง„ ์ตœ์ ํ™”(SEO)๋ผ๋Š” ๋‘ ๋งˆ๋ฆฌ ํ† ๋ผ๋ฅผ ๋ชจ๋‘ ์žก์„ ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•œ ๋ฌด๊ธฐ๊ฐ€ ๋œ๋‹ต๋‹ˆ๋‹ค.

๐Ÿš€ PPR ์‹ค์ „ ์ ์šฉ ๊ฐ€์ด๋“œ

Next.js 14.1๋ถ€ํ„ฐ PPR์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์–ด์š”. ๋ณ„๋„์˜ ์„ค์ • ์—†์ด Suspense์™€ async/await๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ํŽ˜์นญ์„ ํ•˜๋ฉด ์ž๋™์œผ๋กœ PPR์ด ์ ์šฉ๋  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ข€ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ์ดํ•ดํ•˜๊ณ  ์ œ์–ดํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณผ๊ฒŒ์š”.

0๏ธโƒฃ App Router์—์„œ PPR ํ™œ์„ฑํ™” (Next.js 14.1+๋ถ€ํ„ฐ ๊ธฐ๋ณธ)

App Router๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, Next.js 14.1 ๋ฒ„์ „ ์ด์ƒ๋ถ€ํ„ฐ๋Š” PPR์ด ๊ธฐ๋ณธ์ ์œผ๋กœ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์–ด์š”. ํŠน๋ณ„ํ•œ ์„ค์ •์„ ํ•  ํ•„์š” ์—†์ด Suspense๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Next.js๊ฐ€ ์ž๋™์œผ๋กœ ํŽ˜์ด์ง€์˜ ์ •์  ๋ถ€๋ถ„๊ณผ ๋™์  ๋ถ€๋ถ„์„ ๊ตฌ๋ถ„ํ•˜์—ฌ ์ฒ˜๋ฆฌํ•ด ์ค˜์š”.

์œ ์šฉํ•œ ํŒ

๋งŒ์•ฝ Next.js 14.0 ๋ฒ„์ „์„ ์‚ฌ์šฉ ์ค‘์ด๊ฑฐ๋‚˜, ๋ช…์‹œ์ ์œผ๋กœ PPR์„ ํ™œ์„ฑํ™”ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด next.config.js ํŒŒ์ผ์— experimental.ppr: true ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์–ด์š”. ํ•˜์ง€๋งŒ 14.1+ ๋ฒ„์ „์—์„œ๋Š” ๊ธฐ๋ณธ๊ฐ’์ด๋ฏ€๋กœ ์ƒ๋žตํ•ด๋„ ๊ดœ์ฐฎ์•„์š”.

1๏ธโƒฃ Suspense์™€ fallback ํ™œ์šฉ

PPR์˜ ํ•ต์‹ฌ์€ React.Suspense ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋™์ ์ธ ๋ถ€๋ถ„์„ ๊ฐ์‹ธ๋Š” ๊ฒƒ์ด์—์š”. Suspense๋Š” ๋น„๋™๊ธฐ ์ž‘์—…(๋ฐ์ดํ„ฐ ํŽ˜์นญ ๋“ฑ)์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ fallback UI๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ , ์ž‘์—…์ด ์™„๋ฃŒ๋˜๋ฉด ์‹ค์ œ ์ฝ˜ํ…์ธ ๋ฅผ ๋ Œ๋”๋งํ•ด์š”.

import { Suspense } from 'react'; export default function Page() { return ( <main> <h1>๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ ์ œ๋ชฉ</h1> <p>์ด๊ฒƒ์€ ๊ฒŒ์‹œ๋ฌผ์˜ ์ •์ ์ธ ๋‚ด์šฉ์ด์—์š”.</p> <Suspense fallback={<p>๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...</p>}> <CommentsSection /> </Suspense> <Suspense fallback={<p>๊ด€๋ จ ๊ฒŒ์‹œ๋ฌผ์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...</p>}> <RelatedPosts /> </Suspense> </main> ); } async function CommentsSection() { // ์‹ค์ œ๋กœ๋Š” DB๋‚˜ API์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋น„๋™๊ธฐ ํ•จ์ˆ˜ const comments = await fetchComments(); return ( <section> <h2>๋Œ“๊ธ€</h2> {comments.map((comment, index) => ( <p key={index}>{comment.text}</p> ))} </section> ); } async function RelatedPosts() { // ์‹ค์ œ๋กœ๋Š” DB๋‚˜ API์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋น„๋™๊ธฐ ํ•จ์ˆ˜ const related = await fetchRelatedPosts(); return ( <section> <h2>๊ด€๋ จ ๊ฒŒ์‹œ๋ฌผ</h2> <ul> {related.map((post, index) => ( <li key={index}>{post.title}</li> ))} </ul> </section> ); } // ๊ฐ€์ƒ์˜ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ํŽ˜์นญ ํ•จ์ˆ˜ async function fetchComments() { return new Promise(resolve => setTimeout(() => { resolve([{ text: '์ฒซ ๋ฒˆ์งธ ๋Œ“๊ธ€์ด์—์š”!' }, { text: '์ •๋ง ์œ ์šฉํ•œ ์ •๋ณด๋„ค์š”.' }]); }, 2000)); } async function fetchRelatedPosts() { return new Promise(resolve => setTimeout(() => { resolve([{ title: 'Next.js ์บ์‹ฑ ์ „๋žต' }, { title: 'React ๋ Œ๋”๋ง ์ตœ์ ํ™”' }]); }, 1000)); }

์œ„ ์ฝ”๋“œ์—์„œ CommentsSection๊ณผ RelatedPosts ์ปดํฌ๋„ŒํŠธ๋Š” ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ํŽ˜์นญ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์–ด์š”. ์ด ์ปดํฌ๋„ŒํŠธ๋“ค์„ Suspense๋กœ ๊ฐ์‹ธ๋ฉด, Next.js๋Š” ์ดˆ๊ธฐ ์š”์ฒญ ์‹œ <h1>๊ณผ <p> ํƒœ๊ทธ๋กœ ์ด๋ฃจ์–ด์ง„ ์ •์  ์…ธ์„ ๋จผ์ € ๋ณด๋‚ด๊ณ , CommentsSection๊ณผ RelatedPosts์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์ค€๋น„๋˜๋ฉด ํ•ด๋‹น ๋ถ€๋ถ„์„ ์ŠคํŠธ๋ฆฌ๋ฐํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์— ์ „์†กํ•ด์š”. ์‚ฌ์šฉ์ž๋Š” "๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘..."์ด๋ผ๋Š” fallback UI๋ฅผ ๋ณด๋‹ค๊ฐ€ ์‹ค์ œ ๋Œ“๊ธ€์ด ๋‚˜ํƒ€๋‚˜๋Š” ๊ฒฝํ—˜์„ ํ•˜๊ฒŒ ๋˜๋Š” ๊ฑฐ์ฃ .

2๏ธโƒฃ unstable_noStore() ๋˜๋Š” revalidate ์˜ต์…˜์œผ๋กœ ๋™์  ๋ถ€๋ถ„ ๋ช…์‹œ

Next.js๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹œํ•˜๋ ค๊ณ  ์‹œ๋„ํ•ด์š”. PPR์„ ์ œ๋Œ€๋กœ ํ™œ์šฉํ•˜๋ ค๋ฉด, ๋™์ ์œผ๋กœ ๋ Œ๋”๋ง๋˜์–ด์•ผ ํ•  ๋ถ€๋ถ„์ด ์บ์‹œ๋˜์ง€ ์•Š๋„๋ก ๋ช…์‹œํ•ด ์ค„ ํ•„์š”๊ฐ€ ์žˆ์–ด์š”.

  • unstable_noStore(): App Router์—์„œ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ญ์ƒ ์ตœ์‹  ์ƒํƒœ๋กœ ์œ ์ง€ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•ด์š”. ์ด ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ์ˆœ๊ฐ„, ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์™€ ๊ทธ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋“ค์€ ๋™์ ์œผ๋กœ ๋ Œ๋”๋ง๋˜๋„๋ก ํ‘œ์‹œ๋ผ์š”.

    import { unstable_noStore } from 'next/cache'; async function DynamicContent() { unstable_noStore(); // ์ด ์ปดํฌ๋„ŒํŠธ๋Š” ํ•ญ์ƒ ๋™์ ์œผ๋กœ ๋ Œ๋”๋ง๋˜๋„๋ก ๋ช…์‹œ const data = await fetch('https://api.example.com/dynamic-data', { cache: 'no-store' }); const json = await data.json(); return <p>๋™์  ๋ฐ์ดํ„ฐ: {json.value}</p>; }
  • fetch์˜ cache: 'no-store' ๋˜๋Š” revalidate ์˜ต์…˜: fetch API๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, cache: 'no-store' ์˜ต์…˜์„ ์ฃผ๋ฉด ํ•ด๋‹น ์š”์ฒญ์˜ ๊ฒฐ๊ณผ๊ฐ€ ์บ์‹œ๋˜์ง€ ์•Š๊ณ  ํ•ญ์ƒ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋„๋ก ํ•  ์ˆ˜ ์žˆ์–ด์š”. ๋˜ํ•œ, revalidate ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ์‹œ๊ฐ„๋งˆ๋‹ค ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ๊ฒ€์ฆํ•˜๋„๋ก ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ์–ด์š”.

    async function AlwaysFreshData() { const res = await fetch('https://api.example.com/always-fresh', { cache: 'no-store', // ์ด ๋ฐ์ดํ„ฐ๋Š” ์ ˆ๋Œ€ ์บ์‹œํ•˜์ง€ ์•Š๊ณ  ํ•ญ์ƒ ์ตœ์‹ ์„ ๊ฐ€์ ธ์™€์š”. }); const data = await res.json(); return <p>ํ•ญ์ƒ ์ตœ์‹  ๋ฐ์ดํ„ฐ: {data.value}</p>; } async function RevalidatedData() { const res = await fetch('https://api.example.com/revalidated-data', { next: { revalidate: 60 }, // 60์ดˆ๋งˆ๋‹ค ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ๊ฒ€์ฆํ•ด์š”. }); const data = await res.json(); return <p>60์ดˆ๋งˆ๋‹ค ๊ฐฑ์‹ ๋˜๋Š” ๋ฐ์ดํ„ฐ: {data.value}</p>; }

3๏ธโƒฃ ์ฝ”๋“œ ์˜ˆ์‹œ: ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ ์ƒ์„ธ ํŽ˜์ด์ง€์— PPR ์ ์šฉ

์‹ค์ œ ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ ์ƒ์„ธ ํŽ˜์ด์ง€์— PPR์„ ์ ์šฉํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์‚ดํŽด๋ณผ๊ฒŒ์š”. ๊ฒŒ์‹œ๋ฌผ ๋‚ด์šฉ์€ ์ •์ ์ด์ง€๋งŒ, ๋Œ“๊ธ€ ์„น์…˜์€ ๋™์ ์œผ๋กœ ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ๋˜์–ด์•ผ ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด์š”.

// app/posts/[slug]/page.tsx import { Suspense } from 'react'; import { unstable_noStore } from 'next/cache'; // ๋™์  ๋ Œ๋”๋ง์„ ๋ช…์‹œํ•˜๊ธฐ ์œ„ํ•จ import { ErrorBoundary } from 'react-error-boundary'; // ์—๋Ÿฌ ํ•ธ๋“ค๋ง์„ ์œ„ํ•ด // ๊ฐ€์ƒ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋˜๋Š” API ํ˜ธ์ถœ ํ•จ์ˆ˜ async function getPostBySlug(slug: string) { // ์‹ค์ œ๋กœ๋Š” DB์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋กœ์ง const posts = { 'first-post': { title: '์ฒซ ๋ฒˆ์งธ ๊ฒŒ์‹œ๋ฌผ', content: '์ด๊ฒƒ์€ ์ฒซ ๋ฒˆ์งธ ๊ฒŒ์‹œ๋ฌผ์˜ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.' }, 'second-post': { title: '๋‘ ๋ฒˆ์งธ ๊ฒŒ์‹œ๋ฌผ', content: '๋‘ ๋ฒˆ์งธ ๊ฒŒ์‹œ๋ฌผ ๋‚ด์šฉ๋„ ์•„์ฃผ ์ค‘์š”ํ•ด์š”.' }, }; await new Promise(resolve => setTimeout(resolve, 50)); // ๋„คํŠธ์›Œํฌ ์ง€์—ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ return posts[slug] || null; } async function getCommentsForPost(postId: string) { unstable_noStore(); // ๋Œ“๊ธ€์€ ํ•ญ์ƒ ์ตœ์‹  ์ƒํƒœ์—ฌ์•ผ ํ•˜๋ฏ€๋กœ ๋™์ ์œผ๋กœ ์ฒ˜๋ฆฌ // ์‹ค์ œ๋กœ๋Š” API ํ˜ธ์ถœ ๋˜๋Š” DB ์ฟผ๋ฆฌ const comments = { 'first-post': [ { id: 1, author: '๋ธ”๋ฃจ', text: '์ •๋ง ์œ ์ตํ•œ ๊ธ€์ด์—์š”!' }, { id: 2, author: '๋…์ž1', text: 'Next.js PPR ์ดํ•ด์— ํฐ ๋„์›€์ด ๋์–ด์š”.' }, ], 'second-post': [ { id: 3, author: '๋ธ”๋ฃจ', text: '๋‘ ๋ฒˆ์งธ ๊ฒŒ์‹œ๋ฌผ๋„ ์—ด์‹ฌํžˆ ์ž‘์„ฑํ–ˆ์–ด์š”.' }, ], }; await new Promise(resolve => setTimeout(resolve, 1500)); // ๋Œ“๊ธ€ ๋กœ๋”ฉ ์ง€์—ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ return comments[postId] || []; } // ๋Œ“๊ธ€ ์„น์…˜ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ async function Comments({ postId }: { postId: string }) { const comments = await getCommentsForPost(postId); if (!comments || comments.length === 0) { return <p>์•„์ง ๋Œ“๊ธ€์ด ์—†์–ด์š”. ์ฒซ ๋Œ“๊ธ€์„ ๋‚จ๊ฒจ์ฃผ์„ธ์š”!</p>; } return ( <section className="mt-8 p-4 bg-gray-50 rounded-lg"> <h2 className="text-2xl font-bold mb-4">๐Ÿ’ฌ ๋Œ“๊ธ€</h2> {comments.map((comment) => ( <div key={comment.id} className="mb-3 pb-3 border-b border-gray-200 last:border-b-0"> <p className="font-semibold">{comment.author}</p> <p className="text-gray-700">{comment.text}</p> </div> ))} </section> ); } // ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ๋ณด์—ฌ์ค„ UI function CommentsErrorFallback({ error }: { error: Error }) { return ( <div role="alert" className="text-red-600 p-4 border border-red-300 rounded-lg"> <p>โš ๏ธ ๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์–ด์š”: {error.message}</p> </div> ); } export default async function PostPage({ params }: { params: { slug: string } }) { const post = await getPostBySlug(params.slug); if (!post) { return { notFound: true, }; } return ( <article className="max-w-3xl mx-auto py-8 px-4"> <h1 className="text-4xl font-extrabold mb-4 text-gray-900">{post.title}</h1> <p className="text-lg text-gray-700 leading-relaxed whitespace-pre-wrap">{post.content}</p> {/* ๋Œ“๊ธ€ ์„น์…˜์— Suspense์™€ ErrorBoundary ์ ์šฉ */} <ErrorBoundary fallbackRender={({ error }) => <CommentsErrorFallback error={error} />}> <Suspense fallback={ <div className="mt-8 p-4 bg-gray-50 rounded-lg text-gray-600 animate-pulse"> <p>โณ ๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ด์—์š”...</p> </div> }> <Comments postId={params.slug} /> </Suspense> </ErrorBoundary> </article> ); } // generateStaticParams๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋นŒ๋“œ ์‹œ์ ์— ์ •์  ๊ฒฝ๋กœ ์ƒ์„ฑ export async function generateStaticParams() { // ์‹ค์ œ๋กœ๋Š” DB์—์„œ ๋ชจ๋“  ํฌ์ŠคํŠธ์˜ slug๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋กœ์ง return [ { slug: 'first-post' }, { slug: 'second-post' }, ]; }

์œ„ ์˜ˆ์‹œ์—์„œ PostPage๋Š” generateStaticParams๋ฅผ ํ†ตํ•ด ๋นŒ๋“œ ์‹œ์ ์— ์ •์  ์…ธ๋กœ ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ์–ด์š”. ๊ฒŒ์‹œ๋ฌผ์˜ ์ œ๋ชฉ๊ณผ ๋‚ด์šฉ์€ ์ •์  ์…ธ์— ํฌํ•จ๋˜์–ด ๋น ๋ฅด๊ฒŒ ๋กœ๋”ฉ๋˜์ฃ . ๋ฐ˜๋ฉด Comments ์ปดํฌ๋„ŒํŠธ๋Š” unstable_noStore()๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  Suspense๋กœ ๊ฐ์‹ธ์ ธ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ดˆ๊ธฐ์—๋Š” fallback UI๊ฐ€ ๋ณด์ด๋‹ค๊ฐ€ ๋Œ“๊ธ€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋”ฉ๋˜๋ฉด ๋™์ ์œผ๋กœ ์ŠคํŠธ๋ฆฌ๋ฐ๋˜์–ด ํŽ˜์ด์ง€์— ๋‚˜ํƒ€๋‚˜์š”. ErrorBoundary๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋Œ“๊ธ€ ๋กœ๋”ฉ ์ค‘ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์—๋Ÿฌ๋„ gracefullyํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ์–ด์š”.

๐Ÿ’ก PPR ์ตœ์ ํ™” ํŒ๊ณผ ๊ณ ๋ ค์‚ฌํ•ญ

0๏ธโƒฃ Suspense ๊ฒฝ๊ณ„์˜ ์ค‘์š”์„ฑ

Suspense ๊ฒฝ๊ณ„๋Š” PPR์˜ ์„ฑ๋Šฅ์— ๋งค์šฐ ์ค‘์š”ํ•œ ์˜ํ–ฅ์„ ๋ฏธ์ณ์š”. ๋„ˆ๋ฌด ํฐ ๋‹จ์œ„๋ฅผ Suspense๋กœ ๊ฐ์‹ธ๋ฉด ๋™์  ์ฝ˜ํ…์ธ ์˜ ์–‘์ด ๋งŽ์•„์ ธ ์ดˆ๊ธฐ ๋กœ๋”ฉ ์†๋„ ์ด์ ์„ ์žƒ์„ ์ˆ˜ ์žˆ๊ณ , ๋„ˆ๋ฌด ์ž‘์€ ๋‹จ์œ„๋ฅผ ๊ฐ์‹ธ๋ฉด ๋ณต์žก๋„๊ฐ€ ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ์–ด์š”. ์‚ฌ์šฉ์ž์—๊ฒŒ ์˜๋ฏธ ์žˆ๋Š” ์ตœ์†Œ ๋‹จ์œ„๋กœ Suspense๋ฅผ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ด์š”.
์˜ˆ๋ฅผ ๋“ค์–ด, ํŽ˜์ด์ง€ ์ „์ฒด๋ฅผ Suspense๋กœ ๊ฐ์‹ธ๊ธฐ๋ณด๋‹ค๋Š”, ๋…๋ฆฝ์ ์œผ๋กœ ๋กœ๋”ฉ๋  ์ˆ˜ ์žˆ๋Š” ์œ„์ ฏ์ด๋‚˜ ์„น์…˜ ๋‹จ์œ„๋กœ ๋‚˜๋ˆ„์–ด ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์•„์š”.

1๏ธโƒฃ ๋ฐ์ดํ„ฐ ํŽ˜์นญ ์ „๋žต (React cache, fetch ํ™•์žฅ)

Next.js 13+ App Router์—์„œ๋Š” ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ fetch๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ž๋™์œผ๋กœ ์š”์ฒญ์„ ์บ์‹œํ•˜๊ณ  ์ค‘๋ณต์„ ์ œ๊ฑฐํ•ด ์ค˜์š”. ํ•˜์ง€๋งŒ PPR ํ™˜๊ฒฝ์—์„œ๋Š” ํŠน์ • ๋ฐ์ดํ„ฐ๊ฐ€ ํ•ญ์ƒ ์ตœ์‹ ์ด์–ด์•ผ ํ•˜๋Š”์ง€, ์•„๋‹ˆ๋ฉด ์บ์‹œ๋˜์–ด๋„ ๋˜๋Š”์ง€ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ด์š”.

  • ์บ์‹œ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ: fetch์˜ ๊ธฐ๋ณธ ๋™์ž‘์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, revalidate ์˜ต์…˜์„ ํ†ตํ•ด ์ฃผ๊ธฐ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐฑ์‹ ํ•ด์š”.
  • ์บ์‹œ๊ฐ€ ํ•„์š” ์—†๋Š” ๊ฒฝ์šฐ: fetch์— cache: 'no-store' ์˜ต์…˜์„ ๋ช…์‹œํ•˜๊ฑฐ๋‚˜, unstable_noStore()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋™์ ์œผ๋กœ ๋ Œ๋”๋ง๋˜๋„๋ก ํ•ด์š”.

2๏ธโƒฃ ์—๋Ÿฌ ํ•ธ๋“ค๋ง (ErrorBoundary)

PPR ํ™˜๊ฒฝ์—์„œ๋Š” ๋™์ ์ธ ๋ถ€๋ถ„์ด ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋กœ๋”ฉ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ํ•ด๋‹น ๋ถ€๋ถ„์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์ „์ฒด ํŽ˜์ด์ง€๊ฐ€ ๋ง๊ฐ€์ง€๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ErrorBoundary๋ฅผ ์ ์ ˆํžˆ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ด์š”. Suspense์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์˜๋ฏธ ์žˆ๋Š” fallback UI๋ฅผ ์ œ๊ณตํ•ด์•ผ ํ•ด์š”.

3๏ธโƒฃ SEO์™€ ์ ‘๊ทผ์„ฑ

PPR์€ ์ดˆ๊ธฐ HTML์— ์ •์  ์ฝ˜ํ…์ธ ๊ฐ€ ํฌํ•จ๋˜๋ฏ€๋กœ SEO์— ๋งค์šฐ ์œ ๋ฆฌํ•ด์š”. ๊ฒ€์ƒ‰ ์—”์ง„ ํฌ๋กค๋Ÿฌ๋Š” ์ •์  ์…ธ์„ ํ†ตํ•ด ํŽ˜์ด์ง€์˜ ํ•ต์‹ฌ ๋‚ด์šฉ์„ ๋น ๋ฅด๊ฒŒ ์ธ๋ฑ์‹ฑํ•  ์ˆ˜ ์žˆ์–ด์š”. ๋™์ ์œผ๋กœ ๋กœ๋”ฉ๋˜๋Š” ์ฝ˜ํ…์ธ ๋„ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฐฉ์‹์œผ๋กœ ์ „์†ก๋˜๋ฏ€๋กœ, ํฌ๋กค๋Ÿฌ๊ฐ€ ์ถฉ๋ถ„ํžˆ ๊ธฐ๋‹ค๋ฆฌ๋ฉด ๋‚ด์šฉ์„ ํŒŒ์‹ฑํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ fallback UI๊ฐ€ ๋„ˆ๋ฌด ์˜ค๋ž˜ ๋…ธ์ถœ๋˜๊ฑฐ๋‚˜, ์ค‘์š”ํ•œ ์ฝ˜ํ…์ธ ๊ฐ€ Suspense ๋’ค์— ์ˆจ๊ฒจ์ ธ ์žˆ์–ด ์ดˆ๊ธฐ ๋กœ๋”ฉ์ด ์ง€์—ฐ๋˜๋ฉด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์— ๋ถ€์ •์ ์ธ ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ฃผ์˜ํ•ด์•ผ ํ•ด์š”.

๐Ÿ“ ์ •๋ฆฌํ•˜๋ฉฐ

0๏ธโƒฃ PPR์˜ ๊ฐ•๋ ฅํ•œ ์ด์  ์š”์•ฝ

์˜ค๋Š˜ ์šฐ๋ฆฌ๋Š” Next.js์˜ ํ˜์‹ ์ ์ธ ๊ธฐ๋Šฅ์ธ Partial Prerendering (PPR)์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์‚ดํŽด๋ณด์•˜์–ด์š”. PPR์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐ•๋ ฅํ•œ ์ด์ ์„ ์ œ๊ณตํ•ด์š”.

  • ์ดˆ๊ณ ์† ์ดˆ๊ธฐ ๋กœ๋”ฉ: ์ •์  ์…ธ์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž์—๊ฒŒ ์ฆ‰๊ฐ์ ์ธ ํŽ˜์ด์ง€ ๋กœ๋”ฉ ๊ฒฝํ—˜์„ ์„ ์‚ฌํ•ด์š”.
  • ์‹ค์‹œ๊ฐ„ ๋™์  ์ฝ˜ํ…์ธ : Suspense์™€ ์ŠคํŠธ๋ฆฌ๋ฐ์„ ํ†ตํ•ด ๋™์ ์ธ ๋ฐ์ดํ„ฐ๋ฅผ ์ตœ์‹  ์ƒํƒœ๋กœ ์œ ์ง€ํ•˜๋ฉด์„œ๋„ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ํ†ตํ•ฉํ•ด์š”.
  • ์ตœ์ ์˜ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜: ๋น ๋ฅธ ์ดˆ๊ธฐ ๋กœ๋”ฉ๊ณผ ์ตœ์‹  ๋ฐ์ดํ„ฐ ์ œ๊ณต์œผ๋กœ ์‚ฌ์šฉ์ž ๋งŒ์กฑ๋„๋ฅผ ๋†’์—ฌ์š”.
  • ๊ฐ•๋ ฅํ•œ SEO: ๊ฒ€์ƒ‰ ์—”์ง„ ํฌ๋กค๋Ÿฌ๊ฐ€ ํ•ต์‹ฌ ์ฝ˜ํ…์ธ ๋ฅผ ์‰ฝ๊ฒŒ ์ธ๋ฑ์‹ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค˜์š”.
  • ๊ฐœ๋ฐœ ํŽธ์˜์„ฑ: SSR, SSG, ISR์˜ ์žฅ์ ์„ ํ•œ ๋ฒˆ์— ๋ˆ„๋ฆฌ๋ฉด์„œ๋„ ๋ณต์žกํ•œ ์บ์‹ฑ ์ „๋žต์„ ๋‹จ์ˆœํ™”ํ•  ์ˆ˜ ์žˆ์–ด์š”.

1๏ธโƒฃ ๋‹ค์Œ ๋‹จ๊ณ„: ์—ฌ๋Ÿฌ๋ถ„์˜ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด ๋ณด์„ธ์š”

PPR์€ Next.js App Router๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ์ ํŠธ์— ์›น ์„ฑ๋Šฅ๊ณผ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ•œ ๋‹จ๊ณ„ ๋Œ์–ด์˜ฌ๋ฆด ์ˆ˜ ์žˆ๋Š” ๋งค์šฐ ํšจ๊ณผ์ ์ธ ๋„๊ตฌ์˜ˆ์š”. ํŠนํžˆ ๋ธ”๋กœ๊ทธ, ์ด์ปค๋จธ์Šค ์ƒ์„ธ ํŽ˜์ด์ง€, ๋Œ€์‹œ๋ณด๋“œ ๋“ฑ ์ •์ ์ธ ๋ ˆ์ด์•„์›ƒ ์•ˆ์— ๋™์ ์ธ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•œ ํŽ˜์ด์ง€์— ๊ฐ•๋ ฅํ•˜๊ฒŒ ์ถ”์ฒœํ•ด์š”.
์˜ค๋Š˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์—ฌ๋Ÿฌ๋ถ„์˜ Next.js ํ”„๋กœ์ ํŠธ์— Partial Prerendering์„ ์ ์šฉํ•ด ๋ณด์‹œ๊ณ , ๋”์šฑ ๋น ๋ฅด๊ณ  ๋ฐ˜์‘์„ฑ ์ข‹์€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณด์‹œ๊ธธ ๋ฐ”๋ผ์š”! ๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๋‹ค๋ฉด ์–ธ์ œ๋“ ์ง€ ๋Œ“๊ธ€๋กœ ๋‚จ๊ฒจ์ฃผ์„ธ์š”. ๊ฐœ๋ฐœ์ž ์ปค๋ฎค๋‹ˆํ‹ฐ๋Š” ํ•ญ์ƒ ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ๊ณณ์ด๋‹ˆ๊นŒ์š”.

๐Ÿ“ฎ ์ฐธ๊ณ 

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