diff --git a/Aug/article/Deep-Dive: Cching-and-Revalidating.md b/Aug/article/Deep-Dive: Cching-and-Revalidating.md new file mode 100644 index 0000000..5d512e4 --- /dev/null +++ b/Aug/article/Deep-Dive: Cching-and-Revalidating.md @@ -0,0 +1,451 @@ +# [Deep Dive: Caching and Revalidating ](https://github.com/vercel/next.js/discussions/54075) + +### ๐Ÿ—“๏ธ ๋ฒˆ์—ญ ๋‚ ์งœ: 2024.08.11 + +### ๐Ÿงš ๋ฒˆ์—ญํ•œ ํฌ๋ฃจ: ๋ฒ„๊ฑด๋””(์ „ํƒœํ—Œ) + +--- + +๋‹ค์Œ์€ Next.js App Router์—์„œ ์บ์‹ฑ๊ณผ ์žฌ๊ฒ€์ฆ(revalidating)๊ณผ ๊ด€๋ จํ•˜์—ฌ ์ƒˆ๋กญ๊ฒŒ ๋„์ž…๋œ ํœด๋ฆฌ์Šคํ‹ฑ(heuristics)์— ๋Œ€ํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. ์บ์‹ฑ์ด ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ๋Š”์ง€์— ๋Œ€ํ•ด ๋ชจ๋‘๊ฐ€ ๋™์ผํ•œ ์ดํ•ด๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ด ์ฃผ์ œ์— ๋Œ€ํ•ด ์‹ฌ๋„ ์žˆ๋Š” ๋…ผ์˜๋ฅผ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ๋„์›€์ด ๋  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. + +์ด ๋…ผ์˜๋Š” ์ตœ๊ทผ์— ๊ณต๊ฐœ๋œ [์บ์‹ฑ ๊ด€๋ จ ๋ฌธ์„œ](https://nextjs.org/docs/app/building-your-application/caching)์™€ ๋ณด์™„์ ์œผ๋กœ ์ž‘์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ปค๋ฎค๋‹ˆํ‹ฐ์˜ ์ผ๋ถ€ ๊ตฌ์„ฑ์›๋“ค์ด ํด๋ผ์ด์–ธํŠธ ์ธก ๋ผ์šฐํ„ฐ ์บ์‹œ(client-side router cache)์— ๋Œ€ํ•ด ํ˜ผ๋ž€์„ ๊ฒช๊ฑฐ๋‚˜, ์•ฝ๊ฐ„ ๋‹ค๋ฅธ ๋™์ž‘์„ ์›ํ–ˆ๋˜ ๋ฌธ์ œ์— ๋Œ€ํ•œ ํ›„์† ์ž‘์—…์œผ๋กœ, ์ด ๊ธ€์„ ํ†ตํ•ด ์บ์‹ฑ์ด ์–ด๋–ป๊ฒŒ ์„ค๊ณ„๋˜์—ˆ๋Š”์ง€์— ๋Œ€ํ•œ ๋ฐฐ๊ฒฝ์„ ์„ค๋ช…ํ•˜๊ณ  [์ปค๋ฎค๋‹ˆํ‹ฐ์™€์˜ ๋…ผ์˜](https://github.com/vercel/next.js/issues/42991#issuecomment-1673691751)๋ฅผ ์—ด๊ธฐ ์œ„ํ•œ ๊ธฐํšŒ๋ฅผ ์ œ๊ณตํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค. + +**๋‹ต๋ณ€์„ ์ž‘์„ฑํ•˜์‹œ๊ธฐ ์ „์— ์ด ๊ธ€ ์ „์ฒด๋ฅผ ์ฝ์–ด๋ณด์‹œ๊ธธ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์ด ๋ฌธ๋งฅ์„ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.** + +--- + +## - ๋ฐ์ดํ„ฐ ๋ณ€์ด(Mutations) + +App Router๋Š” ๊ฑฐ์˜ 1๋…„ ์ „์— ์ฒ˜์Œ ๋ฐœํ‘œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‹œ์—๋Š” ๋ฐ์ดํ„ฐ ๋ณ€์ด๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•  ๊ณ„ํš์ธ์ง€์— ๋Œ€ํ•ด ์•„์ง ๊ณต์œ ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๊ทธ๋•Œ๋Š” Server Actions๊ฐ€ ์ง€์›๋˜์ง€ ์•Š์•˜๊ณ , `router.refresh()`๊ฐ€ ์˜ˆ์ƒ๋Œ€๋กœ ๋ผ์šฐํŠธ ์บ์‹œ๋ฅผ ์ง€์šฐ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. + +์ด๋กœ ์ธํ•ด ์•ฝ๊ฐ„์˜ ํ˜ผ๋ž€์ด ์žˆ์—ˆ์Œ์„ ์ดํ•ดํ•˜๋ฉฐ, ์ด์— ๋Œ€ํ•ด ๋ช…ํ™•ํžˆ ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ ์ดํ›„๋กœ ์šฐ๋ฆฌ๋Š” [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations)๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์•ˆ์ •ํ™”๋˜๋ฉด ๋Œ€๋ถ€๋ถ„์˜ ๋ณ€์ด๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์œผ๋กœ ๊ธฐ๋Œ€๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, `router.refresh()`๊ฐ€ ์˜ˆ์ƒ๋Œ€๋กœ ์ž‘๋™ํ•˜๋„๋ก ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค. + +์•„๋ž˜์—์„œ๋Š” ๊ฐ ์š”์†Œ์— ๋Œ€ํ•œ ์„ค๋ช…๊ณผ ๊ทธ๊ฒƒ๋“ค์ด ์–ด๋–ป๊ฒŒ ํ•จ๊ป˜ ์ž‘๋™ํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ๋„๊ตฌ์—์„œ ์‚ฌ์šฉํ–ˆ๋˜ ๋ช‡ ๊ฐ€์ง€ ๋™์ž‘(๋•Œ๋กœ๋Š” ์•”๋ฌต์ ์œผ๋กœ ์‚ฌ์šฉ๋œ)์„ ์–ด๋–ป๊ฒŒ ์žฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š”์ง€์— ๋Œ€ํ•œ ์„น์…˜์„ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ์„น์…˜์€ ๋‘ ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋‰˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค: + +- ๋‚ด๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ๋ณ€์ด, ์˜ˆ๋ฅผ ๋“ค์–ด ํผ ์ œ์ถœ ๋“ฑ. +- ๋™๋ฃŒ์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ๋ณ€์ด, ์˜ˆ๋ฅผ ๋“ค์–ด CMS์—์„œ ์ œํ’ˆ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ณ€๊ฒฝ ์‚ฌํ•ญ. + +## - ๋‚ด๊ฐ€ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ๋ณ€์ด + +### ๋ฌดํšจํ™”(Invalidation) + +**Server Actions (revalidatePath / revalidateTag)** + +- ์–ธ์ œ ์œ ์šฉํ•œ๊ฐ€ + + - ์„œ๋ฒ„๊ฐ€ ํ•„์š”ํ•œ ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ + + - ์˜ˆ์‹œ: ํผ(form) ์ œ์ถœ + + - ์ œํ’ˆ ์ถ”๊ฐ€ + - ์‚ฌ์šฉ์ž ์„ธ์…˜์˜ ์ผ๋ถ€๋กœ ์ œํ’ˆ์„ ์ถ”๊ฐ€ํ•œ ํ›„, revalidateTag ๋˜๋Š” revalidatePath๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํŽ˜์ด์ง€๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์žฅ๋ฐ”๊ตฌ๋‹ˆ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ Œ๋”๋ง๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ตœ์‹  ์ƒํƒœ๋กœ ์œ ์ง€๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(useSWR / react-query์˜ mutate()์™€ ์œ ์‚ฌํ•œ ๋™์ž‘). + + - ๋กœ๊ทธ์ธ / ๋กœ๊ทธ์•„์›ƒ + ํผ์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž ์ด๋ฆ„/๋น„๋ฐ€๋ฒˆํ˜ธ(๋˜๋Š” ๋‹ค๋ฅธ ์ธ์ฆ ํ๋ฆ„)๋ฅผ ์ œ์ถœํ•˜๊ณ , ์„œ๋ฒ„ ์•ก์…˜์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•œ ํ›„, revalidatePath ๋˜๋Š” revalidateTag๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ผ์šฐํ„ฐ ์บ์‹œ๊ฐ€ ์‚ญ์ œ๋˜๊ณ , ์ƒˆ๋กœ ์„ค์ •๋œ ์ฟ ํ‚ค๋กœ ์ƒˆ ๋ Œ๋”๋ง์ด ์ด๋ฃจ์–ด์ง€๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. + + - ์˜ˆ์‹œ: ํŽ˜์ด์ง€ ๋‚ด ํƒ€์ดํ”„์–ดํ—ค๋“œ(์‹ค์‹œ๊ฐ„ ๊ฒ€์ƒ‰) / ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ƒ์ž + - ์˜ˆ๋ฅผ ๋“ค์–ด [nextjs.org/docs](https://nextjs.org/docs)์—์„œ ๊ฒ€์ƒ‰ํ•  ๋•Œ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ(ํ˜„์žฌ๋Š” Algolia์— ์˜ํ•ด ์ง€์›๋˜์ง€๋งŒ, ์ด๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋กœ ์ง€์›ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค). + + - ์˜ˆ์‹œ: ๊ถŒํ•œ์— ๊ธฐ๋ฐ˜ํ•œ ํˆดํŒ + - Facebook, Instagram, GitHub์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์˜ˆ์‹œ๋กœ, ํˆดํŒ์ด ํด๋ฆญ ์‹œ ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ์— ๊ธฐ๋ฐ˜ํ•ด ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค. + +- ์ž‘๋™ ์›๋ฆฌ + +1. `"use server"`๋Š” ์ปดํŒŒ์ผ ์‹œ ํ•จ์ˆ˜์— ๋งˆ์ปค๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ๋ณ„๋„์˜ ๋ชจ๋“ˆ๋กœ ์ด๋™์‹œํ‚ต๋‹ˆ๋‹ค. ๋ชจ๋“  Server Actions๋Š” ๊ฐ๊ฐ ๊ณ ์œ ํ•œ ID๋ฅผ ๊ฐ€์ง€๋ฏ€๋กœ, ๊ฐœ๋ณ„์ ์œผ๋กœ ์ฐธ์กฐ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ด ์„ค๋ช…์€ ๋งค์šฐ ๋‹จ์ˆœํ™”๋œ ๋ฒ„์ „์ด๋ฉฐ, ์‹ค์ œ๋กœ๋Š” ๋” ๋ณต์žกํ•ฉ๋‹ˆ๋‹ค.) + +2. ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, ํ˜„์žฌ URL๋กœ Server Action ID๋ฅผ ํฌํ•จํ•œ POST ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด Next.js๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ ๊ธฐ๋ณธ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + +- ํ•จ์ˆ˜ ํ˜ธ์ถœ ์˜ˆ์‹œ + `
`์ด ์ œ์ถœ๋˜๊ฑฐ๋‚˜, ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ง์ ‘ `serverAction()`์„ ํ˜ธ์ถœํ•˜๋Š” ๊ฒฝ์šฐ + ์„œ๋ฒ„ ์•ก์…˜์œผ๋กœ ์ •์˜๋œ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ๋ช‡ ๊ฐ€์ง€ ์ผ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +- ํ•จ์ˆ˜๊ฐ€ `revalidatePath` ๋˜๋Š” `revalidateTag`๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. + + - ์ง€์ •๋œ ํ•ญ๋ชฉ์ด ์ „์ฒด ๋ผ์šฐํŠธ ์บ์‹œ ๋˜๋Š” ๋ฐ์ดํ„ฐ ์บ์‹œ(์„œ๋ฒ„ ์บ์‹œ)์—์„œ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. + - ๋ผ์šฐํŠธ๊ฐ€ ์ •์  ๋ Œ๋”๋ง(์ „์ฒด ๋ผ์šฐํŠธ ์บ์‹œ ๋‚ด) ์ค‘์ผ ๋•Œ ์‚ฌ์šฉ๋œ ๋ชจ๋“  fetch ํƒœ๊ทธ๋Š” ์ „์ฒด ๋ผ์šฐํŠธ ์บ์‹œ์˜ ํƒœ๊ทธ์ด๊ธฐ๋„ ํ•˜๋ฏ€๋กœ, revalidateTag๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์ „์ฒด ๋ผ์šฐํŠธ ์บ์‹œ๋„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. + +- `revalidatePath`์— ์žˆ๋Š” ๊ฒฝ๋กœ์— ๋”ฐ๋ผ ํ˜„์žฌ ํŽ˜์ด์ง€๋Š” ์„œ๋ฒ„์—์„œ ๋‹ค์‹œ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. `revalidateTag`์˜ ๊ฒฝ์šฐ, ํ˜„์žฌ ํŽ˜์ด์ง€๋Š” ํ•ญ์ƒ ๋‹ค์‹œ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. + + - ์ด ๋™์ž‘์ด `router.refresh()`๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ๊ณผ ๋งค์šฐ ์œ ์‚ฌํ•˜๋‹ค๋Š” ๊ฒƒ์„ ๋‚˜์ค‘์— ๊นจ๋‹ซ๊ฒŒ ๋  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + - ์ด๋Š” ๋™์ผํ•œ ๋ผ์šด๋“œ ํŠธ๋ฆฝ์˜ ์ผ๋ถ€๋กœ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ ์ถ”๊ฐ€์ ์ธ ํด๋ผ์ด์–ธํŠธ ์ธก ์š”์ฒญ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +- ์ œ๊ณต๋œ ๊ฒฝ๋กœ์— ๋”ฐ๋ผ ํด๋ผ์ด์–ธํŠธ ์ธก ์บ์‹œ๊ฐ€ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. `revalidateTag`์˜ ๊ฒฝ์šฐ, ์ „์ฒด ํด๋ผ์ด์–ธํŠธ ์ธก ๋ผ์šฐํ„ฐ ์บ์‹œ๊ฐ€ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. + + - Server Actions๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜ˆ์‹œ + +```tsx +export async function updateUser(config) { + "use server"; + await prisma.user.update(config); + revalidateTag("prisma-user"); +} +``` + +- ์ด ์Šค๋ ˆ๋“œ์—์„œ ํ˜ผ๋ž€์ด ๋งŽ์•˜๋˜ ์ด์œ ๋Š” ๊ตฌํ˜„ ๊ณผ์ •์—์„œ ๋ช‡ ๊ฐ€์ง€ ๋ฒ„๊ทธ๊ฐ€ ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฒ„๊ทธ๋Š” ์ตœ์‹  ๋ฒ„์ „์˜ Next.js์—์„œ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค. (์˜ˆ๋ฅผ ๋“ค์–ด, ์ด์ „์—๋Š” ๋’ค๋กœ/์•ž์œผ๋กœ ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์„ ์‚ฌ์šฉํ•  ๋•Œ์˜ ์บ์‹œ๊ฐ€ ์‚ญ์ œ๋˜์ง€ ์•Š๊ณ , ํ”„๋ฆฌํŽ˜์น˜๋œ ๋ฐ์ดํ„ฐ๋งŒ ์‚ญ์ œ๋˜์–ด ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚จ์•„ ์žˆ์„ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ๋Š” ๋” ์ด์ƒ ๊ทธ๋ ‡์ง€ ์•Š์ง€๋งŒ, ์ด ์ ์„ ๊ฐ•์กฐํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.) + +- ์ด ๋™์ž‘์€ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ์˜ fetch ์š”์ฒญ์ด ๋ฐ˜ํ™˜๋œ ํ›„ `router.refresh()`์™€ ์‚ฌ์‹ค์ƒ ๋™์ผํ•œ ์ฝ”๋“œ ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ, ๋ผ์šฐํ„ฐ ์บ์‹œ๋ฅผ ์‚ญ์ œํ•˜๋Š” ๋ฐฉ์‹๋„ ๋ณธ์งˆ์ ์œผ๋กœ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. + + - ๋ฏธ๋ž˜์—๋Š” ์บ์‹œ ์‚ญ์ œ ๋ฐฉ์‹์„ ๋” ์Šค๋งˆํŠธํ•˜๊ฒŒ ๊ฐœ์„ ํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ˜„์žฌ `/products` ๊ฒฝ๋กœ์— ์žˆ๊ณ  ์„œ๋ฒ„ ์•ก์…˜์—์„œ `revalidatePath('/dashboard/settings')`๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์ „์ฒด ๋ผ์šฐํ„ฐ ์บ์‹œ๊ฐ€ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” `/dashboard/settings` ๊ฒฝ๋กœ์™€ ๊ทธ ํ•˜์œ„ ์„ธ๊ทธ๋จผํŠธ๋งŒ ์‚ญ์ œํ•˜๋Š” ๊ฒƒ์ด ๋” ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค. + + - ์—ฌ๊ธฐ์„œ "ํ•˜์œ„ ์„ธ๊ทธ๋จผํŠธ"๋ž€ `/dashboard/settings/`environment์™€ ๊ฐ™์€ ๊ฒฝ๋กœ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์ œ๊ณต๋œ ๊ฒฝ๋กœ ์•„๋ž˜์˜ ๋ ˆ์ด์•„์›ƒ์ด ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—, ํ•ด๋‹น ๊ฒฝ๋กœ์— ๋Œ€ํ•œ ๋ผ์šฐํ„ฐ ์บ์‹œ๋ฅผ ์‚ญ์ œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + + - ์ด์™€ ๊ฐ™์€ ์ด์œ ๋กœ, `revalidatePath('/')`๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋ผ์šฐํ„ฐ ์บ์‹œ์— ์žˆ๋Š” ๋ชจ๋“  ๊ฒฝ๋กœ๊ฐ€ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” `/` ๊ฒฝ๋กœ์™€ ๊ทธ ํ•˜์œ„ ์„ธ๊ทธ๋จผํŠธ ๋ชจ๋‘๊ฐ€ ํฌํ•จ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ `router.refresh()`๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ `revalidatePath('/')`์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ผ์šฐํ„ฐ ์บ์‹œ๋ฅผ ์‚ญ์ œํ•˜๊ณ  ๋‹ค์‹œ ์ฑ„์šฐ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. + +- ํ•จ์ˆ˜๊ฐ€ `redirect()`๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒฝ์šฐ + + - redirect()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, ์„œ๋ฒ„ ์•ก์…˜์—์„œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๋ฆฌ๋””๋ ‰์…˜์ด ์ฆ‰์‹œ ์ ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +- ํ•จ์ˆ˜๊ฐ€ `cookies().set()`์„ ํ˜ธ์ถœํ•˜๋Š” ๊ฒฝ์šฐ + + - ์ด ๊ฒฝ์šฐ, ๋ผ์šฐํ„ฐ ์บ์‹œ๊ฐ€ ์™„์ „ํžˆ ๋ฌดํšจํ™”๋˜๊ณ  ํŽ˜์ด์ง€๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” `revalidateTag`๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ๊ณผ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์ฟ ํ‚ค๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ๋ ˆ์ด์•„์›ƒ์—์„œ ๋ Œ๋”๋ง๋˜๋Š” ๋‚ด์šฉ์— ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + + - ์˜ˆ์‹œ: ๋กœ๊ทธ์ธ / ๋กœ๊ทธ์•„์›ƒ + - ์˜ˆ์‹œ: ๊ธฐ๋Šฅ ํ”Œ๋ž˜๊ทธ(Feature flags) ๋ณ€๊ฒฝ + +- ํ•จ์ˆ˜๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ + + - ์„œ๋ฒ„ ์•ก์…˜์—์„œ ์ง์ ‘ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์‘๋‹ต์€ ๊ฐ์ฒด์ผ ์ˆ˜๋„ ์žˆ๊ณ , JSX์ผ ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ, ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋กœ ์ง๋ ฌํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ฒƒ์ด ์„œ๋ฒ„ ์•ก์…˜์˜ ์œ ํšจํ•œ ๋ฐ˜ํ™˜ ๊ฐ’์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + + - ์ด ๊ฒฝ์šฐ์—๋„ revalidatePath ๋˜๋Š” revalidateTag๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์„ ๊ธˆ์ง€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ด๋Ÿฌํ•œ ํ˜ธ์ถœ์ด ์ด๋ฃจ์–ด์ง„๋‹ค๋ฉด, ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ์˜ ์‘๋‹ต์€ ํ•จ์ˆ˜์˜ ๊ฒฐ๊ณผ์™€ ์ƒˆ๋กœ ๋ Œ๋”๋ง๋œ ํŽ˜์ด์ง€๋ฅผ ๋ชจ๋‘ ํฌํ•จํ•˜๊ฒŒ ๋˜๋ฉฐ, ๋‘ ๊ฐ€์ง€๊ฐ€ ๋ชจ๋‘ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. + + - ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์„œ๋ฒ„ ์•ก์…˜์„ ์ˆ˜๋™์œผ๋กœ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ``์—์„œ ์„œ๋ฒ„ ์•ก์…˜์„ `action` ์†์„ฑ์— ์ง์ ‘ ์ „๋‹ฌํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ํ•ด๋‹น๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +- **Route Handlers (revalidatePath / revalidateTag)** + +์–ธ์ œ ์œ ์šฉํ•œ๊ฐ€: + +- ์™ธ๋ถ€ ์ด๋ฒคํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์„œ๋ฒ„ ์ธก ์บ์‹œ(์ „์ฒด ๊ฒฝ๋กœ ์บ์‹œ ๋ฐ ๋ฐ์ดํ„ฐ ์บ์‹œ)๋ฅผ ์‚ญ์ œํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. + + - ์˜ˆ์‹œ: CMS์—์„œ ํ•ญ๋ชฉ์„ ์ˆ˜์ •ํ•  ๋•Œ, ํ˜„์žฌ์˜ ์ „์ฒด ๊ฒฝ๋กœ ์บ์‹œ/๋ฐ์ดํ„ฐ ์บ์‹œ๋ฅผ ์‚ญ์ œํ•˜์—ฌ ์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์ „์ฒด ๊ฒฝ๋กœ ์บ์‹œ/๋ฐ์ดํ„ฐ ์บ์‹œ์— ์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ์ฑ„์šฐ๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + - ๋น„๋ก ์บ์‹ฑ ๋ฌธ์„œ์—์„œ ์ „์ฒด ๊ฒฝ๋กœ ์บ์‹œ์™€ ๋ฐ์ดํ„ฐ ์บ์‹œ๋ฅผ ๋ณ„๋„๋กœ ์„ค๋ช…ํ•˜๊ณ  ์žˆ์ง€๋งŒ, ์‹ค์ œ๋กœ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํ•˜๋‚˜์˜ ๋ฐ์ดํ„ฐ ์บ์‹œ์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ์ด๋ฅผ ๋‘ ๊ฐœ์˜ ๊ฐœ๋…์œผ๋กœ ๋ฌธ์„œํ™”ํ•œ ์ฃผ๋œ ์ด์œ ๋Š” ์ „์ฒด ๊ฒฝ๋กœ ์บ์‹œ ๋ถ€๋ถ„์€ HTML/RSC ํŽ˜์ด๋กœ๋“œ๋ฅผ ์ €์žฅํ•˜๊ณ , ๋ฐ์ดํ„ฐ ์บ์‹œ ๋ถ€๋ถ„์€ fetches์™€ unstable_cache ํ˜ธ์ถœ์„ ์ €์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค(์ด API๋Š” ๊ฐ€๊นŒ์šด ๋ฏธ๋ž˜์— ์•ˆ์ •ํ™”๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค). + +- ์ž‘๋™ ๋ฐฉ์‹ + + - `revalidatePath` ๋˜๋Š” `revalidateTag`๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์ง€์ •๋œ ํ•ญ๋ชฉ์ด ์ „์ฒด ๊ฒฝ๋กœ ์บ์‹œ/๋ฐ์ดํ„ฐ ์บ์‹œ์—์„œ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. + + - ํ•ด๋‹น ๊ฒฝ๋กœ๊ฐ€ ์ •์  ๋ Œ๋”๋ง(์ „์ฒด ๊ฒฝ๋กœ ์บ์‹œ์— ์žˆ์Œ)์ผ ๋•Œ, ์‚ฌ์šฉ๋œ ๋ชจ๋“  fetch ํƒœ๊ทธ๊ฐ€ ์ „์ฒด ๊ฒฝ๋กœ ์บ์‹œ์˜ ํƒœ๊ทธ๋กœ๋„ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ฆ‰, revalidateTag๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์ „์ฒด ๊ฒฝ๋กœ ์บ์‹œ๋„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. + + - ์‚ฌ์šฉ์ž์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๊ฒฝ๋กœ ํ•ธ๋“ค๋Ÿฌ๋ฅผ fetch()ํ•˜๋Š” ๊ฒฝ์šฐ, Next.js์—์„œ๋Š” ์–ด๋–ค ๊ฒƒ์ด ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ๋ผ์šฐํ„ฐ ์บ์‹œ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ, router.refresh()๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + + - ๊ทธ๋Ÿฌ๋‚˜, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ API ์—”๋“œํฌ์ธํŠธ์ธ ๊ฒฝ๋กœ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ˜ธ์ถœํ•  ์˜ˆ์ •์ด๋ผ๋ฉด, ๋‘ ๋ฒˆ์˜ ์™•๋ณต ๋Œ€์‹  ํ•œ ๋ฒˆ์˜ ์™•๋ณต์œผ๋กœ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„ ์•ก์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +```js +onClick={async () => { + const res = await fetch('/my-route-handler') + const json = await res.json() + // etc + router.refresh() +}} +``` + +**router.refresh()** + +- ์–ธ์ œ ์œ ์šฉํ•œ๊ฐ€ + + - ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ์œผ๋กœ ์ธํ•ด ๋ฐœ์ƒํ•˜๋Š” ์„œ๋ฒ„ ์•ก์…˜์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š๋Š” ์ด๋ฒคํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ผ์šฐํ„ฐ ์บ์‹œ๋ฅผ ์‚ญ์ œํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. + + - ์˜ˆ์‹œ: ํผ์ด๋‚˜ ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์™ธ๋ถ€ API๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜์—ฌ ์˜ˆ๋ฅผ ๋“ค์–ด ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒฝ์šฐ, ์„œ๋ฒ„ ์•ก์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ์ƒํ™ฉ์ž…๋‹ˆ๋‹ค. ์ด๋Š” useSWR ๋˜๋Š” react-query๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ mutate()๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์™€ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. + - ์ด ์ƒํ™ฉ์—์„œ๋Š” ์„œ๋ฒ„ ์•ก์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์Šต๋‹ˆ๋‹ค. ์œ„์˜ ์˜ˆ์‹œ์—์„œ๋Š” ๋‘ ๋ฒˆ์˜ ์™•๋ณต(ํ•œ ๋ฒˆ์€ API์—, ํ•œ ๋ฒˆ์€ ์—…๋ฐ์ดํŠธ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜์˜ํ•œ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ์ž‘์—…)์ด ํ•„์š”ํ•˜์ง€๋งŒ, ์„œ๋ฒ„ ์•ก์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ์ด ์ž‘์—…์„ ํ•œ ๋ฒˆ์˜ ์™•๋ณต์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์•ก์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•˜๊ณ  `revalidatePath` ๋˜๋Š” `revalidateTag`๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋™์ผํ•œ ์„œ๋ฒ„ ์š”์ฒญ์—์„œ ์ƒˆ ๊ฒฐ๊ณผ๋ฅผ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์„œ๋ฒ„ ์•ก์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํ•ญ์ƒ ๊ฐ€๋Šฅํ•˜์ง€๋Š” ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +- ํŽ˜์ด์ง€๊ฐ€ ๋ Œ๋”๋ง๋œ ํ›„ ์ตœ์‹  ํ•ญ๋ชฉ์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜๋Š” stale-while-revalidate (useSWR/react-query) ๋™์ž‘์„ ์›ํ•  ๋•Œ๋„ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. + +- ์˜ˆ์‹œ: ์•„๋ž˜ ์ฝ”๋“œ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ์ด, ์ด๋Š” useSWR๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๋Š” ์ž‘์—…๊ณผ ๋งค์šฐ ์œ ์‚ฌํ•˜์ง€๋งŒ, ์—ฌ๊ธฐ์„œ๋Š” revalidation์„ ์–ธ์ œ, ์–ด๋–ป๊ฒŒ ํŠธ๋ฆฌ๊ฑฐํ• ์ง€ ์™„์ „ํžˆ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ ์€ ๋ผ์šฐํ„ฐ ์บ์‹œ์—๋งŒ ์˜ํ–ฅ์„ ๋ฏธ์น˜๋ฉฐ, ์„œ๋ฒ„ ์ธก ์บ์‹œ์—๋Š” ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +```tsx +import { useRouter, usePathname } from "next/navigation"; +import { debounce } from "lodash"; + +interface DataRevalidatorProps { + /** + * ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๊ฒ€์ฆํ•˜๊ธฐ ์ „์— ๋Œ€๊ธฐํ•  ์‹œ๊ฐ„(๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„)์ž…๋‹ˆ๋‹ค. + */ + timeout?: number; +} + +export function DataRevalidator({ + timeout = 5000, +}: DataRevalidatorProps): null { + const router = useRouter(); + const pathname = usePathname(); + + const getRefresher = useCallback( + () => + debounce( + () => { + // ์ด ์ฝ”๋“œ๋Š” ์–ธ์ œ router.refresh()๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋˜๋Š”์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€๋œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + // ์‹ค์ œ๋กœ๋Š” ์ œ๊ฑฐํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค. + console.log( + `[${new Date().toLocaleTimeString()}] Triggering router.refresh()...` + ); + router.refresh(); + }, + timeout, + { leading: false, trailing: true } + ), + [router, timeout] + ); + + // `pathname`์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค `router.refresh()`๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. + // ์ฐธ๊ณ : ์ด ์ฝ”๋“œ๋Š” `searchParams`๋ฅผ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•„์š”์‹œ `useSearchParams`๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + useEffect(() => { + const refresher = getRefresher(); + refresher(); + }, [revalidate, pathname]); + + // ์ฐฝ์œผ๋กœ ํฌ์ปค์Šค๊ฐ€ ๋Œ์•„์˜ฌ ๋•Œ `router.refresh()`๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. + // (์ด๋Š” `useSWR` ๋˜๋Š” `react-query`์™€ ์œ ์‚ฌํ•œ ๋™์ž‘์ž…๋‹ˆ๋‹ค.) + useEffect(() => { + if (process.env.NODE_ENV === "development") { + return; + } + + const refresher = getRefresher(); + + window.addEventListener("focus", refresher); + return () => { + refresher.cancel(); + window.removeEventListener("focus", refresher); + }; + }, [revalidate]); + + return null; +} +``` + +- ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ• ๊นŒ์š” + + - router.refresh()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ผ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค: + + - ์„œ๋ฒ„๋กœ ํ˜„์žฌ ํŽ˜์ด์ง€ ์ „์ฒด๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ ์š”์ฒญ์ด ์ „์†ก๋ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ, HTML ๋Œ€์‹  RSC Payload ํ˜•์‹์œผ๋กœ ํŽ˜์ด์ง€์˜ ๋ชจ๋“  ๋‚ด์šฉ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. + - ๋ผ์šฐํ„ฐ์˜ ๋ชจ๋“  ์บ์‹œ๊ฐ€ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค: + - ํ”„๋ฆฌํŽ˜์น˜ ์บ์‹œ(๋ชจ๋“  ํ”„๋ฆฌํŽ˜์น˜๋ฅผ ์ €์žฅํ•˜๋Š” ์บ์‹œ) + - ๋ผ์šฐํ„ฐ ๋„ค๋น„๊ฒŒ์ด์…˜ ์บ์‹œ(30์ดˆ ๋™์•ˆ ์œ ์ง€๋˜๋Š” ์บ์‹œ) + - ์ปดํฌ๋„ŒํŠธ ์บ์‹œ(๋„ค๋น„๊ฒŒ์ด์…˜ํ–ˆ๋˜ ๋ชจ๋“  ์„ธ๊ทธ๋จผํŠธ๋ฅผ ์ €์žฅํ•˜๋ฉฐ, ๋ถ€๋ถ„ ๋ Œ๋”๋ง ๋ฐ ์Šคํฌ๋กค ์œ„์น˜๋ฅผ ๋ณด์กดํ•˜๋ฉด์„œ ๋’ค๋กœ/์•ž์œผ๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ์บ์‹œ) + - ๊ฐ€์ ธ์˜จ RSC Payload๊ฐ€ ์ œ๊ฑฐ๋œ ์บ์‹œ์— ์ ์šฉ๋˜๋ฉฐ, ์ด๋กœ ์ธํ•ด ํ•ด๋‹น ์บ์‹œ๋Š” ์œ ์ผํ•œ ๊ฒฝ๋กœ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค + +## ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ๋ณ€๋™ ์‚ฌํ•ญ (Mutations) + +--- + +### ๋ฐ์ดํ„ฐ ์†Œ์Šค ๋ณ€๊ฒฝ + +๋ณ€๋™ ์‚ฌํ•ญ(Mutations)์— ๊ด€ํ•œ ์ด์•ผ๊ธฐ์˜ ๋‹ค๋ฅธ ์ธก๋ฉด์œผ๋กœ, ๋งŒ์•ฝ ๋‚ด๊ฐ€ ์•„๋‹Œ ๋‚ด ๋™๋ฃŒ๊ฐ€ CMS์—์„œ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•œ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”? ๋งŒ์•ฝ ๊ทธ ๋ณ€๋™ ์‚ฌํ•ญ์ด `revalidatePath` ๋˜๋Š” `revalidateTag`๋กœ ๋ฐฑ์—…๋˜์—ˆ๋‹ค๋ฉด, ์ด๋Š” ์ „์ฒด ๊ฒฝ๋กœ ์บ์‹œ์™€ ๋ฐ์ดํ„ฐ ์บ์‹œ๋ฅผ ๋ฌดํšจํ™”ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๊ฒƒ๋“ค์€ ์„œ๋ฒ„ ์ธก์— ์žˆ๋Š” ๊ฒƒ์ด๋ฉฐ, ๋‚ด ๋ธŒ๋ผ์šฐ์ € ํƒญ์— ์žˆ๋Š” ๋ผ์šฐํ„ฐ ์บ์‹œ๋Š” ์—ฌ์ „ํžˆ ์„œ๋ฒ„์˜ ์ด์ „ ๊ฒฐ๊ณผ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ๊ฐ€์žฅ ์ตœ์‹  ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ˜์˜๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ์ด์Šˆ์— ๋Œ€ํ•œ ๋Œ“๊ธ€ ์ค‘ ์ƒ๋‹น์ˆ˜๋Š” ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ๋ณ€๋™ ์‚ฌํ•ญ์„ ๋งŒ๋“ค์—ˆ์„ ๋•Œ ์–ด๋–ค ์ผ์ด ์ผ์–ด๋‚˜๋Š”์ง€์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•˜๊ณ  ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋จผ์ € ์ด ์ƒํ™ฉ์—์„œ์˜ ๋™์ž‘์„ ๋ช…ํ™•ํžˆ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค: + +- ๋‚ด๊ฐ€ `/products` ๊ฒฝ๋กœ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. +- ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ์„œ๋ฒ„ ์•ก์…˜์„ ํŠธ๋ฆฌ๊ฑฐํ•˜์—ฌ ์ „์ž์ƒ๊ฑฐ๋ž˜ ์Šคํ† ์–ด์— ์ƒˆ ์ œํ’ˆ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. +- ๋‚˜๋Š” ๋ฉ”๋‰ด์—์„œ ๊ณ„์ •(account)์„ ํด๋ฆญํ•˜์—ฌ `/account` ๊ฒฝ๋กœ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. +- ๋‚˜๋Š” ๋‹ค์‹œ ์ œํ’ˆ์„ ๋ณด๊ณ  ์‹ถ์–ด์ ธ์„œ, [https://demo.vercel.store](https://demo.vercel.store) ์—์„œ ๊ทธ ํ›„๋“œํ‹ฐ๋ฅผ ์ •๋ง๋กœ ์‚ฌ๊ณ  ์‹ถ์–ดํ•ฉ๋‹ˆ๋‹ค. +- ๋‚˜๋Š” ๋ฉ”๋‰ด์—์„œ ์ œํ’ˆ(products)์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค. +- ๋‚˜๋Š” ์ฆ‰์‹œ /products ๊ฒฝ๋กœ๋กœ ๋‹ค์‹œ ์ด๋™ํ•˜๋ฉฐ, ์ด๋Š” ๋งˆ์น˜ ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•œ ๊ฒƒ๊ณผ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค. +- ๋‚ด๊ฐ€ ์ฒ˜์Œ /products ๊ฒฝ๋กœ๋ฅผ ์—ด์—ˆ์„ ๋•Œ ๋ณด์•˜๋˜ ๊ฒƒ๊ณผ ๋™์ผํ•œ ํŽ˜์ด์ง€๊ฐ€ ๋ณด์ž…๋‹ˆ๋‹ค. + +๊ฒฝ๋กœ๊ฐ€ "์ •์ "์ธ ๊ฒฝ์šฐ(์˜ˆ: ์ •์  ๋ Œ๋”๋ง ๋˜๋Š” ํŽ˜์ด์ง€๊ฐ€ ๋™์  ๋ Œ๋”๋ง์„ ์‚ฌ์šฉํ•  ๋•Œ `prefetch={true}`๋กœ ์„ค์ •ํ•œ ๊ฒฝ์šฐ), `/products`๋กœ ์ฆ‰์‹œ ๋‹ค์‹œ ๋Œ์•„๊ฐˆ ์ˆ˜ ์žˆ๋Š” ์‹œ๊ฐ„์€ 5๋ถ„์ž…๋‹ˆ๋‹ค. + +๊ฒฝ๋กœ๊ฐ€ ๋™์  ๋ Œ๋”๋ง์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ(์˜ˆ: ์ฟ ํ‚ค, ํ—ค๋” ์‚ฌ์šฉ, `export const dynamic = 'force-dynamic'` ์„ค์ •) `/products`๋กœ ์ฆ‰์‹œ ๋‹ค์‹œ ๋Œ์•„๊ฐˆ ์ˆ˜ ์žˆ๋Š” ์‹œ๊ฐ„์€ 30์ดˆ์ž…๋‹ˆ๋‹ค. + +์ด 30์ดˆ ๋˜๋Š” 5๋ถ„์ด ์ง€๋‚˜๋ฉด, ํ•ด๋‹น ์บ์‹œ ํ•ญ๋ชฉ์€ router.push / router.replace ๋‚ด๋น„๊ฒŒ์ด์…˜(์—ฌ๊ธฐ์—๋Š” ``๋„ ํฌํ•จ๋จ)์— ๋Œ€ํ•ด ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค. + +์ด์ œ `/products`๊ฐ€ ๋™์  ๋ Œ๋”๋ง์„ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •๋˜์–ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค. ์ด๋Š” ์บ์‹œ ์‹œ๊ฐ„์ด 30์ดˆ์ž„์„ ์˜๋ฏธํ•˜๋ฉฐ, ๋งŒ์•ฝ ๋‚ด๊ฐ€ `/account`์—์„œ ์กฐ๊ธˆ ๋” ์˜ค๋ž˜ ๋จธ๋ฌผ๋ €๋‹ค๋ฉด ์บ์‹œ๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ˆˆ์น˜์ฑ„์ง€ ๋ชปํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +์ด ๋™์ž‘์ด ์กด์žฌํ•˜๋Š” ์ด์œ ๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: + +- ๋ฉ”๋‰ด๋ฅผ ๋น ๋ฅด๊ฒŒ ํด๋ฆญํ•˜๊ฑฐ๋‚˜ ํƒญํ•  ๋•Œ, ๋งํฌ๋ฅผ ํด๋ฆญํ•œ ํ›„ ๋‹ค๋ฅธ ๋งํฌ๋ฅผ ํด๋ฆญํ•˜๊ณ  ๋‹ค์‹œ ๋Œ์•„์™”์„ ๋•Œ ์—ฌ๋Ÿฌ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ๋‹ค์‹œ ๋ณด๋Š” ๊ฒƒ์€ UX์— ์ข‹์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +- ๋Š๋ฆฐ ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์—์„œ ์ด ๋™์ž‘์€ ๋งํฌ๋ฅผ ์‚ฌ์šฉํ•ด ์•ž๋’ค๋กœ ์ด๋™ํ•  ๋•Œ ์—ฌ์ „ํžˆ ๋น ๋ฅด๊ฒŒ ๋Š๊ปด์ง€๋„๋ก ๋ณด์žฅํ•ด์ค๋‹ˆ๋‹ค. + +- ๋˜ํ•œ ๋ผ์šฐํ„ฐ ์‚ฌ์ด๋ฅผ ๋น ๋ฅด๊ฒŒ ์ด๋™ํ•  ๋•Œ ์œ„์น˜๋ฅผ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ๊ณผ๋„ ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค๋ฅธ ํŽ˜์ด์ง€์—์„œ ๋ฌด์–ธ๊ฐ€๋ฅผ ์ฐพ์•„๋ณด๊ณ  ์ด์ „ ํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐ€๋Š” ๊ฒฝ์šฐ์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค. + +์ด ํƒ€์ด๋ฐ๊ณผ ๊ด€๋ จ๋œ ์ด์ „ ์—ฐ๊ตฌ ๋ฐ ์‚ฌ๋ก€๊ฐ€ ์žˆ์œผ๋ฉฐ, ์˜ˆ๋ฅผ ๋“ค์–ด ํŽ˜์ด์Šค๋ถ์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์—์„œ ํด๋ฆญํ•  ๋•Œ ์ด ํƒ€์ด๋ฐ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. react-query๋„ ์ด ํƒ€์ด๋ฐ์„ ์ฑ„ํƒํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +`/products` ๊ฒฝ๋กœ๋Š” ํ—ค๋”/ํ‘ธํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•ด ๋ฐ์ดํ„ฐ ์บ์‹œ๋ฅผ ์—ฌ์ „ํžˆ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด ๊ฒฝ์šฐ ์ œํ’ˆ ์ž์ฒด์— ๋Œ€ํ•œ ์š”์ฒญ๋งŒ์ด ๋ธ”๋กœํ‚น๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. + +์ด๊ฒƒ์ด ๋ผ์šฐํ„ฐ ์บ์‹œ๋ฅผ ๋ฌดํšจํ™”ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ์—ฌ์ „ํžˆ stale-while-revalidate ๋ฐ focus ๋™์ž‘(useSWR / react-query์— ์žˆ๋Š”)์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์œ„์˜ router.refresh ์„น์…˜ ์ฐธ์กฐ). + +์ „์ฒด์ ์œผ๋กœ ์ด ๋™์ž‘์˜ ๊ธฐ๋ณธ ์„ค์ •์ด ์˜ฌ๋ฐ”๋ฅด๋‹ค๊ณ  ๋ฏฟ์ง€๋งŒ, ์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ์‚ฌ๋žŒ๋“ค์ด ์ด ๋™์ž‘์„ ์˜ตํŠธ์•„์›ƒ(opt-out)ํ•˜๋ ค๋Š” ๋ฐฉ๋ฒ•์„ ํŒŒ์•…ํ•˜๊ณ ์ž ํ•˜๋ฉฐ, ์ด ๋™์ž‘๊ณผ ํ˜ผ๋™๋  ์ˆ˜ ์žˆ๋Š” ์—ฌ๋Ÿฌ ๊ด€๋ จ ๊ธฐ๋Šฅ์ด ์žˆ๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. + +## ๋ถ€๋ถ„ ๋ Œ๋”๋ง + +๋ฌธ์„œ์—์„œ ๊ณ ์ˆ˜์ค€ ๊ฐœ๋…์„ ์ž˜ ์„ค๋ช…ํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, [๋ฌธ์„œ์—์„œ ๋ฐœ์ทŒํ•œ ๋‚ด์šฉ](https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#3-partial-rendering)์„ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค: + +๋ถ€๋ถ„ ๋ Œ๋”๋ง(Partial Rendering)์€ ๋„ค๋น„๊ฒŒ์ด์…˜ ์‹œ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ๋กœ ์„ธ๊ทธ๋จผํŠธ๋งŒ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋‹ค์‹œ ๋ Œ๋”๋ง๋˜๊ณ , ๊ณต์œ ๋œ ์„ธ๊ทธ๋จผํŠธ๋Š” ๊ทธ๋Œ€๋กœ ์œ ์ง€๋˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, ๋‘ ํ˜•์ œ ๊ฒฝ๋กœ `/dashboard/settings`์™€ `/dashboard/analytics` ์‚ฌ์ด๋ฅผ ๋„ค๋น„๊ฒŒ์ด์…˜ํ•  ๋•Œ, `์„ค์ • ํŽ˜์ด์ง€(settings)`์™€ `๋ถ„์„ ํŽ˜์ด์ง€(analytics)`๊ฐ€ ๋ Œ๋”๋ง๋˜๋ฉฐ, ๊ณต์œ ๋œ ๋Œ€์‹œ๋ณด๋“œ ๋ ˆ์ด์•„์›ƒ์€ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. + +[partial-rendering.avif](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/33037b65-dd75-41a0-b974-2d73a569d5eb/partial-rendering.avif) + +๋ถ€๋ถ„ ๋ Œ๋”๋ง์ด ์—†์„ ๊ฒฝ์šฐ, ๊ฐ ๋„ค๋น„๊ฒŒ์ด์…˜์€ ์„œ๋ฒ„์—์„œ ์ „์ฒด ํŽ˜์ด์ง€๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋ณ€๊ฒฝ๋˜๋Š” ์„ธ๊ทธ๋จผํŠธ๋งŒ ๋ Œ๋”๋งํ•˜๋ฉด ์ „์†ก๋˜๋Š” ๋ฐ์ดํ„ฐ ์–‘๊ณผ ์‹คํ–‰ ์‹œ๊ฐ„์ด ์ค„์–ด๋“ค์–ด ์„ฑ๋Šฅ์ด ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค. + +์ด๊ฒƒ์ด ๊ฐ€๋Šฅํ•œ ์ด์œ ๋Š” ์•ฑ ๋ผ์šฐํ„ฐ(App Router)๊ฐ€ "๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ(Router Tree)"๋ผ๊ณ  ๋ถ€๋ฅด๋Š” ๊ฐœ๋…์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด ํŠธ๋ฆฌ๋Š” ํ˜„์žฌ ๋ณด๊ณ  ์žˆ๋Š” ํ™”๋ฉด์— ๋ฌด์—‡์ด ๋ Œ๋”๋ง๋˜๋Š”์ง€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ํ˜•์‹์ž…๋‹ˆ๋‹ค. + +๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค (๋‹จ์ˆœํ™”๋œ ํ˜•ํƒœ): + +```ts +// Array format as it's transferred between server/client often. +type RouterTree = [ + // The segment, e.g. 'dashboard' or for dynamic params ['user', 'timneutkens', 'd' /* d for dynamic */]. + // Final segment is called __PAGE__ + segment: Segment + // In the simple case this would only hold `children`. + parallelRoutes: { + // E.g. `children` or `modal` (when you add `@modal` as a parallel route slot). + [parallelRouteKey: string]: RouterTree + } +] +``` + +๊ฐ ๊ฒฝ๋กœ(route)์—๋Š” ๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ(Router Tree)๊ฐ€ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, /dashboard/settings/page.tsx์™€ ๊ฐ™์€ ๊ฒฝ๋กœ์— ๋Œ€ํ•œ ๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค: + +```ts +const routerTreeForSettings: RouterTree = [ + "dashboard", + { + children: [ + "settings", + { + children: ["__PAGE__", {}], + }, + ], + }, +]; +``` + +์ฒ˜์Œ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•  ๋•Œ, ์„œ๋ฒ„ ์ธก์˜ ๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ๋Š” ์ปดํฌ๋„ŒํŠธ ๋ชฉ๋ก(์˜ˆ: `๋ ˆ์ด์•„์›ƒ`, `๋กœ๋”ฉ`, `ํŽ˜์ด์ง€`)์„ ํฌํ•จํ•˜๋ฉฐ, ์ด๋ฅผ ์‚ฌ์šฉํ•ด ์ „์ฒด ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. + +๋„ค๋น„๊ฒŒ์ด์…˜์„ ํ•  ๋•Œ, ํ˜„์žฌ ํ™”๋ฉด์— ์žˆ๋Š” ๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ๊ฐ€ ํŽ˜์ด๋กœ๋“œ์˜ ์ผ๋ถ€๋กœ ์„œ๋ฒ„์— ์ „์†ก๋ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„๋Š” ํ˜„์žฌ ๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ์™€ ์š”์ฒญํ•˜๋Š” ํŽ˜์ด์ง€์˜ ๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ๋ฅผ ๋น„๊ต(diff)ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋น„๊ต ๊ฒฐ๊ณผ๋Š” ๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ์—์„œ ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„๋งŒ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ๋Š” ๋ธŒ๋ผ์šฐ์ € ํžˆ์Šคํ† ๋ฆฌ์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค(๋’ค๋กœ/์•ž์œผ๋กœ ์ด๋™ ์„น์…˜์—์„œ ๋” ์ž์„ธํžˆ ์„ค๋ช…). + +ํ˜„์žฌ ํ™”๋ฉด์— ์žˆ๋Š” ํŠธ๋ฆฌ๋งŒ์œผ๋กœ๋Š” ์ด์ „์— ๋ Œ๋”๋ง๋œ ํŽ˜์ด์ง€์˜ ๊ฐœ๋ณ„ ์„ธ๊ทธ๋จผํŠธ๋ฅผ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋•Œ ํด๋ผ์ด์–ธํŠธ ์ธก ๋ผ์šฐํ„ฐ์˜ "์ปดํฌ๋„ŒํŠธ ์บ์‹œ" ๋ถ€๋ถ„์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +๋ผ์šฐํ„ฐ ์ƒํƒœ์— ๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ ์™ธ์—๋„, ์šฐ๋ฆฌ๋Š” ์ปดํฌ๋„ŒํŠธ ์บ์‹œ๋„ ๊ทธ๊ณณ์— ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ์ด ์บ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค(๋‹จ์ˆœํ™”๋œ ํ˜•ํƒœ). + +```ts +type ChildSegmentMap = Map< + // For explanation purposes showing Segment + // Real value is a serialized version of Segment (string) + Segment, + ComponentCacheNode +>; + +interface ComponentCacheNode { + // There is also 'DATAFETCH' and 'LAZYINITIALIZED' but not going into those in this post. + status: "READY"; + // This property only exists on the `__PAGE__` nodes. + head?: React.ReactNode; + subTreeData: React.ReactNode; + parallelRoutes: Map; +} +``` + +์ปดํฌ๋„ŒํŠธ ์บ์‹œ๋Š” ๊ฐ๊ฐ์˜ ๋ Œ๋”๋ง๋œ ์„ธ๊ทธ๋จผํŠธ๋ฅผ ๊ฐœ๋ณ„์ ์œผ๋กœ ์ถ”์ ํ•˜๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๊ธฐ๋Šฅ์ด ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค: + +- ์„ธ๊ทธ๋จผํŠธ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋ฉฐ ํŽ˜์ด์ง€ ๊ฐ„ ๋„ค๋น„๊ฒŒ์ด์…˜ (๋ถ€๋ถ„ ๋„ค๋น„๊ฒŒ์ด์…˜). +- ๋ธŒ๋ผ์šฐ์ €์˜ ์Šคํฌ๋กค ์œ„์น˜๋ฅผ ๋ณด์กดํ•˜๋Š” ์ฆ‰์‹œ ๋’ค๋กœ/์•ž์œผ๋กœ ์ด๋™. +- ๋ณ‘๋ ฌ ๋ผ์šฐํŠธ/์ธํ„ฐ์…‰์…˜, ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€์˜ ์„ธ๊ทธ๋จผํŠธ ์บ์‹œ ๋…ธ๋“œ๋ฅผ ๋™์‹œ์— ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ๋Š” ๋ณ‘๋ ฌ ๋ผ์šฐํŠธ๋ฅผ ์ž‘๋™ํ•˜๊ฒŒ ํ•˜๋Š” ๋˜ ๋‹ค๋ฅธ ์š”์†Œ๋กœ, ํ™”๋ฉด์— ๋ฌด์—‡์ด ํ‘œ์‹œ๋˜๋Š”์ง€๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. +- ๋ผ์šฐํ„ฐ ์บ์‹œ์˜ ๊ฐœ๋ณ„ ์„ธ๊ทธ๋จผํŠธ ์‚ญ์ œ(์˜ˆ: `router.refresh()`๋Š” ์ „์ฒด ์บ์‹œ๋ฅผ ์‚ญ์ œํ•˜์ง€๋งŒ, `revalidatePath('/dashboard')`๋Š” ๊ฐ€๊นŒ์šด ๋ฏธ๋ž˜์— ์ง€์ •๋œ ๋Œ€์‹œ๋ณด๋“œ ๊ฒฝ๋กœ ์•„๋ž˜์˜ ํด๋ผ์ด์–ธํŠธ ์ธก ๋ผ์šฐํ„ฐ ์บ์‹œ๋งŒ ์‚ญ์ œํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค). + +## ๋’ค๋กœ/์•ž์œผ๋กœ ๋„ค๋น„๊ฒŒ์ด์…˜ + +์ด ๋™์ž‘์€ ๋ธŒ๋ผ์šฐ์ €์— ๋‚ด์žฅ๋œ bfcache์™€ ๋งค์šฐ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค: [https://web.dev/bfcache/](https://web.dev/articles/bfcache?hl=ko). + +๊ฒฝ๋กœ ๊ฐ„ ๋„ค๋น„๊ฒŒ์ด์…˜ ์‹œ ํด๋ผ์ด์–ธํŠธ ์ธก ๋ผ์šฐํ„ฐ๋Š” pushState ๋˜๋Š” replaceState๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ € ์ฃผ์†Œ ํ‘œ์‹œ์ค„์— ๋ณด์ด๋Š” URL์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ผ๋ถ€๋„ ํ•จ๊ป˜ ํ‘ธ์‹œ๋˜๋ฉฐ, ํŠนํžˆ ๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ๊ฐ€ ํžˆ์Šคํ† ๋ฆฌ ํ•ญ๋ชฉ์— ์ฒจ๋ถ€๋ฉ๋‹ˆ๋‹ค. + +๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ๋Š” ํ™”๋ฉด์— ๋ฌด์—‡์ด ๋ Œ๋”๋ง๋ ์ง€๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค(๋” ๊นŠ์€ ์„ค๋ช…์€ ์ด์ „ ์„น์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”). + +๋’ค๋กœ/์•ž์œผ๋กœ ์ด๋™([popstate](https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event)) ์‹œ ํžˆ์Šคํ† ๋ฆฌ ํ•ญ๋ชฉ์˜ ๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ๊ฐ€ ๋ผ์šฐํ„ฐ์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ํ•ด๋‹น ๋ผ์šฐํ„ฐ ํŠธ๋ฆฌ๊ฐ€ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. + +์ปดํฌ๋„ŒํŠธ ์บ์‹œ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ €์˜ ๊ธฐ๋ณธ `scrollRestoration` ๊ธฐ๋Šฅ์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ๋’ค๋กœ/์•ž์œผ๋กœ ์ด๋™ํ•  ๋•Œ ์ •ํ™•ํžˆ ์ด์ „์— ์žˆ๋˜ ์œ„์น˜๋กœ ๋Œ์•„๊ฐˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋ธŒ๋ผ์šฐ์ €์—์„œ์˜ ์ด scrollRestoration ๋™์ž‘์€ ์ผ์ •ํ•œ ํœด๋ฆฌ์Šคํ‹ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, ์ด ์ž‘์—…์„ ์ œ๋Œ€๋กœ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์œผ๋ฉด ์Šคํฌ๋กค ์œ„์น˜ ๋ณต์›์ด ์‹คํŒจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด์ œ Pages Router์™€ ์ด ๋™์ž‘์ด ์–ด๋–ป๊ฒŒ ๋‹ค๋ฅธ์ง€ ๊ถ๊ธˆํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +Pages Router์—์„œ๋Š” ๋‘ ๊ฐ€์ง€ ๋™์ž‘์ด ์žˆ์—ˆ์œผ๋ฉฐ, ๋‘˜ ๋‹ค ํด๋ผ์ด์–ธํŠธ ์ธก ๋ผ์šฐํ„ฐ์— ์ ์šฉ๋  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. + +`getStaticProps` + +- ๋„ค๋น„๊ฒŒ์ด์…˜ ์‹œ, ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐ€์ ธ์™€์ ธ ๋‹จ์ˆœํ•œ ์บ์‹œ์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ์ด ์บ์‹œ๋Š” ๋ณธ์งˆ์ ์œผ๋กœ { [url: string]: Data } ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค. +- ๋’ค๋กœ/์•ž์œผ๋กœ ๋„ค๋น„๊ฒŒ์ด์…˜ ์‹œ, ์ด ์บ์‹œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +- ์ด ๋™์ž‘์—๋Š” ํ•œ ๊ฐ€์ง€ ๋ฌธ์ œ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค: ํด๋ผ์ด์–ธํŠธ ์ธก ๋ผ์šฐํ„ฐ ์บ์‹œ๋ฅผ ๋ฌดํšจํ™”ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—, ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•ด๋„ ์‚ฌ์šฉ์ž๊ฐ€ ํƒญ์„ ์ƒˆ๋กœ ๊ณ ์น˜์ง€ ์•Š์œผ๋ฉด ๊ฒฐ๊ณผ๋ฅผ ๋ณผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ์บ์‹œ๋ฅผ ์‚ญ์ œํ•  API๊ฐ€ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. +- ์Šคํฌ๋กค ์œ„์น˜๋Š” ๋ณต์›๋ฉ๋‹ˆ๋‹ค. + +`getServerSideProps` + +- ๋„ค๋น„๊ฒŒ์ด์…˜ ์‹œ, ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„์— ์š”์ฒญ์ด ์ „์†ก๋ฉ๋‹ˆ๋‹ค. +- ๋’ค๋กœ/์•ž์œผ๋กœ ๋„ค๋น„๊ฒŒ์ด์…˜ ์‹œ์—๋„ ๋ฐ์ดํ„ฐ ์š”์ฒญ์„ ์œ„ํ•ด ์„œ๋ฒ„๋กœ ์š”์ฒญ์ด ์ „์†ก๋ฉ๋‹ˆ๋‹ค. +- ๋’ค๋กœ/์•ž์œผ๋กœ ๋„ค๋น„๊ฒŒ์ด์…˜์ด ์ฆ‰์‹œ ์ ์šฉ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์š”์ฒญ์ด ์ง„ํ–‰ ์ค‘์ผ ๋•Œ ์Šคํฌ๋กค ๋ณต์› ํœด๋ฆฌ์Šคํ‹ฑ์— ๋„๋‹ฌํ•˜๊ธฐ ์ „์ด๊ธฐ ๋•Œ๋ฌธ์—, UX๊ฐ€ ๊นจ์ง„ ๊ฒƒ์ฒ˜๋Ÿผ ๋Š๊ปด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์šฐ๋ฆฌ๋Š” ์Šคํฌ๋กค ์œ„์น˜๊ฐ€ ๋ณด์กด๋˜๊ณ  ๋„ค๋น„๊ฒŒ์ด์…˜์ด ์ฆ‰๊ฐ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋Š” ์ƒˆ๋กœ์šด ๋™์ž‘์ด ์˜ฌ๋ฐ”๋ฅธ ๋™์ž‘์ด๋ผ๊ณ  ๋ฏฟ์Šต๋‹ˆ๋‹ค. ์ด ๋™์ž‘์€ ๋ธŒ๋ผ์šฐ์ €์˜ bfcache ๋™์ž‘์„ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค. + +์ด ๋™์ž‘์—์„œ ๊ธฐ์–ตํ•ด์•ผ ํ•  ์ฃผ์š” ์‚ฌํ•ญ์€, ๋’ค๋กœ/์•ž์œผ๋กœ ๋„ค๋น„๊ฒŒ์ด์…˜ ์‹œ ์‚ฌ์šฉ๋˜๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก ์ปดํฌ๋„ŒํŠธ ์บ์‹œ๋Š” `router.refresh()`, `revalidatePath`, `revalidateTag`์™€ ๊ฐ™์€ ๋ฌดํšจํ™” ํ•จ์ˆ˜๋“ค์„ ํ˜ธ์ถœํ•  ๋•Œ๋งŒ ์‚ญ์ œ๋œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. + +์ด๋Š” ์ด์ „์— ํ•ด์™”๋˜ ๊ฒƒ๊ณผ ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ฆ‰, ํด๋ผ์ด์–ธํŠธ ์ธก SPA๋ฅผ ์™„์ „ํžˆ ๊ตฌ์ถ•ํ•  ๋•Œ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŽ˜์น˜ ๊ฒฐ๊ณผ๋ฅผ ์ถ”์ ํ•˜๋Š” ์ž์ฒด ์บ์‹œ๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒƒ๊ณผ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์บ์‹œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์‚ญ์ œ๋˜์–ด์•ผ ํ•˜๋ฉฐ, ์ด๋Š” useSWR ๋˜๋Š” react-query์—์„œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. + +## ๋ผ์šฐํ„ฐ์— ๋Œ€ํ•œ ๋‹จ๊ธฐ ์ž‘์—… + +ํ˜„์žฌ ์šฐ๋ฆฌ์˜ ์ฃผ์š” ์ดˆ์ ์€ ์•ฑ ๋ผ์šฐํ„ฐ(App Router)์˜ ์•ˆ์ •์„ฑ์„ ๊ฐœ์„ ํ•˜๋Š” ๋ฐ ์žˆ์œผ๋ฉฐ, ๋ณด๊ณ ๋œ ๋ฒ„๊ทธ๋ฅผ ์กฐ์‚ฌํ•˜๊ณ  ์ˆ˜์ •ํ•˜๋Š” ๋ฐ ์ƒ๋‹นํ•œ ์‹œ๊ฐ„์„ ํ• ์• ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ž‘์—…์ด ์™„๋ฃŒ๋˜๋ฉด ํด๋ผ์ด์–ธํŠธ ์ธก ๋ผ์šฐํ„ฐ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•˜๋Š” ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ์ž‘์—…์ด ์ง„ํ–‰๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค. + +### ์ •์  ๋ ˆ์ด์•„์›ƒ ์ตœ์ ํ™” + +ํ˜„์žฌ ๊ฒฝ๋กœ๊ฐ€ ์™„์ „ํžˆ ์ •์ ์ผ ๋•Œ(์ •์  ๋ Œ๋”๋ง), RSC ํŽ˜์ด๋กœ๋“œ(RSC Payload)์— ๋Œ€ํ•ด URL์„ ๊ฐ€์ ธ์˜ค๊ณ , [์ „์ฒด ๊ฒฝ๋กœ ์บ์‹œ์—์„œ HIT(์บ์‹œ ์ ์ค‘)](https://nextjs.org/docs/app/building-your-application/caching#full-route-cache)๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์š”์ฒญ์ด ์ •์  ํŒŒ์ผ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด ์ƒ๋‹นํžˆ ๋น ๋ฅด๊ฒŒ ์ œ๊ณต๋œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด ์‘๋‹ต์€ ๋ ˆ์ด์•„์›ƒ ๋ Œ๋”๋ง์„ ๋ถ„ํ• ํ•จ์œผ๋กœ์จ ๋” ์ตœ์ ํ™”๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ตฌ์ฒด์ ์œผ๋กœ, ๊ฐ ๋ ˆ์ด์•„์›ƒ์— ๋Œ€ํ•ด RSC ํŽ˜์ด๋กœ๋“œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋Š” ๋™์  ๋ Œ๋”๋ง์„ ์ˆ˜ํ–‰ํ•  ๋•Œ์ฒ˜๋Ÿผ ๋” ์„ธ๋ถ„ํ™”๋œ ๋„ค๋น„๊ฒŒ์ด์…˜์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. + +### ๋„ค๋น„๊ฒŒ์ด์…˜/์ƒˆ๋กœ๊ณ ์นจ ๋ฐฐ์นญ ์ฒ˜๋ฆฌ + +ํ˜„์žฌ ๋„ค๋น„๊ฒŒ์ด์…˜ ํŽ˜์น˜๋Š” ์—ฌ๋Ÿฌ React ์ „ํ™˜(React Transitions) ๊ฐ„ ์ƒํƒœ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์œ ์ง€๋˜๋„๋ก ์ˆœ์ฐจ์ ์œผ๋กœ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ React Experimental์—์„œ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์ด ์žˆ์œผ๋ฉฐ(์„œ๋ฒ„ ์•ก์…˜์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค), ์ด ๊ธฐ๋Šฅ์€ startTransition ํ•จ์ˆ˜ ์ž์ฒด๋ฅผ ๋น„๋™๊ธฐ(async)๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ณ , React๋Š” ์—ฌ๋Ÿฌ ์ „ํ™˜์„ ๋™์‹œ์— ํŠธ๋ฆฌ๊ฑฐํ•˜๋ฉด์„œ ๊ทธ ์ˆœ์„œ๋ฅผ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ด ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์—ฌ ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœํ•˜๋Š” ๊ฒฝ์šฐ์˜ ์†๋„๋ฅผ ๋†’์ผ ๊ณ„ํš์ž…๋‹ˆ๋‹ค. ์˜ˆ๋กœ, ์ž…๋ ฅ ํ•„๋“œ์— ํƒ€์ดํ•‘ํ•  ๋•Œ searchParams๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ์„œ๋ฒ„ ์•ก์…˜์˜ ๋ฐฐ์นญ ์ฒ˜๋ฆฌ + +ํ˜„์žฌ ์„œ๋ฒ„ ์•ก์…˜์€ ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์ผ๊ด€์„ฑ์„ ์œ„ํ•ด ํ˜ธ์ถœ๋œ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰๋˜์–ด์•ผ ํ•˜์ง€๋งŒ, ์—ฌ๋Ÿฌ ์•ก์…˜์„ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ๋‹จ์ผ ์š”์ฒญ์œผ๋กœ ์„œ๋ฒ„์— ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ํด๋ผ์ด์–ธํŠธ/์˜คํ”„๋ผ์ธ ์•„์ผ๋žœ๋“œ + +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ผ๋ถ€ ์„น์…˜์„ SPA(๋‹จ์ผ ํŽ˜์ด์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜)์™€ ์œ ์‚ฌํ•˜๊ฒŒ ํ‘œ์‹œํ•˜์—ฌ ๋„ค๋น„๊ฒŒ์ด์…˜ ์‹œ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด์ง€ ์•Š๋„๋ก ํ•˜๋Š” ์ œ์•ˆ์„ ์ž‘์—… ์ค‘์ž…๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ฅธ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๋Š” ํ•ด๋‹น "์•„์ผ๋žœ๋“œ"(๊ฒฝ๋กœ ์ง‘ํ•ฉ)์—์„œ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ฐ€๋Šฅํ•œ JS์˜ ๋งค๋‹ˆํŽ˜์ŠคํŠธ๋ฅผ ๋ณด๋‚ด์•ผ ํ•œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. + +## ๊ฒฐ๋ก  + +์ด์ œ ๋™์  ๋ Œ๋”๋ง RSC ํŽ˜์ด๋กœ๋“œ์˜ 30์ดˆ ์บ์‹ฑ์„ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +๋จผ์ €, ํ˜„์žฌ ๋™์ž‘์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฒ„๊ทธ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ํ”ผ๋“œ๋ฐฑ์„ ์ฝ์–ด๋ณด๋‹ˆ, 30์ดˆ๊ฐ€ ์ง€๋‚˜๊ธฐ ์ „์— ํŽ˜์ด์ง€๋กœ ๋‹ค์‹œ ๋„ค๋น„๊ฒŒ์ด์…˜ํ•  ๋•Œ๋งˆ๋‹ค ์บ์‹œ ์‹œ๊ฐ„์ด 30์ดˆ์”ฉ ์ถ”๊ฐ€๋˜๋Š” ๋ฒ„๊ทธ๊ฐ€ ์žˆ์Œ์„ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•  ์˜ˆ์ •์ด๋ฉฐ, ํŽ˜์ด์ง€์—์„œ ๋ฒ—์–ด๋‚œ ์‹œ์ ๋ถ€ํ„ฐ 30์ดˆ๋กœ ๋ณ€๊ฒฝํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, `/account`๋กœ ๋„ค๋น„๊ฒŒ์ด์…˜ํ•œ ํ›„ 10์ดˆ๋ฅผ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ `/products`๋กœ ๋Œ์•„์˜ค๋ฉด ์‹œ๊ฐ„์ด 30์ดˆ ์ถ”๊ฐ€๋˜์ง€ ์•Š๊ณ , ๋Œ€์‹  20์ดˆ ํ›„์— ์บ์‹œ ๋…ธ๋“œ๊ฐ€ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค(10์ดˆ ํ›„์— ๋‹ค์‹œ ํด๋ฆญํ•œ ๊ฒฝ์šฐ). + +๋ชจ๋“  ๊ฒƒ์ด ๊ธฐ๋ณธ์ ์œผ๋กœ ๋™์ ์œผ๋กœ ์„ค์ •๋œ ๋˜ ๋‹ค๋ฅธ ์„ ํƒ์ง€๊ฐ€ ์žˆ์—ˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋„ค๋น„๊ฒŒ์ด์…˜ํ•  ๋•Œ๋งˆ๋‹ค ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฃจํŠธ๋ถ€ํ„ฐ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. + +์ด๋Š” Pages Router์—์„œ getServerSideProps๊ฐ€ ์ž‘๋™ํ•˜๋Š” ๋ฐฉ์‹๊ณผ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ช‡ ๊ฐ€์ง€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด: + +- ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„๋กœ ๋” ๋งŽ์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด๋™์‹œํ‚ฌ์ˆ˜๋ก RSC ํŽ˜์ด๋กœ๋“œ๊ฐ€ ์ฆ๊ฐ€ํ•˜๊ฒŒ ๋˜๋ฉฐ, ๋„ค๋น„๊ฒŒ์ด์…˜์ด๋‚˜ ๋ณ€๋™(mutation) ๋“ฑ ์–ด๋–ค ์ž‘์—…์„ ํ•  ๋•Œ๋งˆ๋‹ค ์ด๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. + - ํŠนํžˆ ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€์—์„œ ๊ณต์œ ๋˜๋Š” ๋ ˆ์ด์•„์›ƒ์ด ์žˆ์„ ๋•Œ ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๊ฐ€ ๋” ๋‘๋“œ๋Ÿฌ์ง‘๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ``์„ ํฌํ•จํ•˜๋Š” ๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ์ด๋‚˜ ๋Œ€์‹œ๋ณด๋“œ ํŽ˜์ด์ง€ ๊ฐ„ ๋„ค๋น„๊ฒŒ์ด์…˜ ์‹œ์˜ ๋Œ€์‹œ๋ณด๋“œ ๋ ˆ์ด์•„์›ƒ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ถ€๋ถ„ ๋„ค๋น„๊ฒŒ์ด์…˜(Partial Navigation)์ด ์—†๋‹ค๋ฉด ์ด๋Ÿฌํ•œ ๋ชจ๋“  ์ƒํ˜ธ์ž‘์šฉ์— ๋Œ€ํ•ด ``๋ถ€ํ„ฐ ๋‹ค์‹œ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. + - ์ด๋กœ ์ธํ•ด ์ƒ๋‹นํ•œ ๋Œ€์—ญํญ ์‚ฌ์šฉ์ด ๋ฐœ์ƒํ•˜๋ฉฐ, ์ด๋Š” ๋Š๋ฆฐ ๋„คํŠธ์›Œํฌ์—์„œ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๊ณ  ๋น„์šฉ์„ ์ฆ๊ฐ€์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +- ์•ž์œผ๋กœ ์šฐ๋ฆฌ๋Š” ์ด๋Ÿฌํ•œ ๋ถ€๋ถ„ ๋„ค๋น„๊ฒŒ์ด์…˜์„ ๋” ์ž‘๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํˆดํŒ, ๊ฒ€์ƒ‰ ์ƒ์ž, ํƒญ, ํŽ˜์ด์ง€๋„ค์ด์…˜, ์•„์ฝ”๋””์–ธ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ์— ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ ๋˜๋Š” ์†Œ์ˆ˜์˜ ์ปดํฌ๋„ŒํŠธ๋งŒ ๋ Œ๋”๋ง๋˜๋„๋ก ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + - ์ด๋Ÿฌํ•œ ๋„ค๋น„๊ฒŒ์ด์…˜์ด ํŽ˜์ด์ง€์™€ ๋ ˆ์ด์•„์›ƒ์„ ๋‹ค์‹œ ๊ฐ€์ ธ์™€์•ผ ํ• ์ง€ ์—ฌ๋ถ€๋Š” ์•„์ง ๋ช…ํ™•ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ผ์šฐํ„ฐ ์บ์‹œ๊ฐ€ ์—†๊ณ  ๋ชจ๋“  ์ƒํ˜ธ์ž‘์šฉ์ด ํŽ˜์ด์ง€๋ฅผ ์™„์ „ํžˆ ๋‹ค์‹œ ๋ Œ๋”๋งํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ํˆดํŒ์„ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด์„œ๋ผ๋„ ๋ชจ๋“  ๊ฒฝ๋กœ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ ํŽ˜์นญ์ด ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋ฏ€๋กœ ์ด ํŒจํ„ด์€ ๋Š๋ ค์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +- ๋ชจ๋“  ๊ฒƒ์ด ๋™์ ์œผ๋กœ ๋ Œ๋”๋ง๋˜๊ณ  ์บ์‹ฑ ๋ฐ ๋ถ€๋ถ„ ๋„ค๋น„๊ฒŒ์ด์…˜์ด ์—†์„ ๊ฒฝ์šฐ, ์„œ๋ฒ„์—์„œ ํ•ญ์ƒ ์ „์ฒด ํŽ˜์ด์ง€๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ(์˜ˆ: ์„œ๋“œํŒŒํ‹ฐ npm ๋ชจ๋“ˆ ํฌํ•จ)๊ฐ€ ์ž์ฒด ์บ์‹ฑ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๋„์ž…ํ•˜์—ฌ ์†๋„๋ฅผ ์œ ์ง€ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, react-tweet์€ ๋””์Šคํฌ๋‚˜ ์™ธ๋ถ€ ์ €์žฅ์†Œ์— ๊ธฐ๋กํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + - ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์บ์‹œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ, ๊ฒฐ๊ตญ ์ง์ ‘ ์บ์‹œ๋ฅผ ๋„์ž…ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, useSWR ๋˜๋Š” react-query๋Š” ์ž์ฒด ์บ์‹ฑ ๋™์ž‘์„ ๋„์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค. ํ˜น์€ Redux์™€ ๊ฐ™์€ ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ•  ๋•Œ๋งˆ๋‹ค ๊ด€๋ฆฌํ•ด์•ผ ํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก ์บ์‹œ๋ฅผ ์Šค์Šค๋กœ ๊ตฌํ˜„ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค(mutate() ๋˜๋Š” dispatch() ํ˜ธ์ถœ ๋“ฑ). + - ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” SPA์—์„œ๋Š” ๊ดœ์ฐฎ์„ ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ผ์šฐํ„ฐ๊ฐ€ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ๋ฐฉ์‹์— ๊นŠ์ด ํ†ตํ•ฉ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์ด๋Ÿฌํ•œ ๋„ค๋น„๊ฒŒ์ด์…˜/์บ์‹œ๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ถ”์ ํ•˜๋Š” ๊ฒƒ์€ ๋ฒˆ๊ฑฐ๋กœ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋•Œ๋ฌธ์— react-query์˜ ๊ด€๋ฆฌ์ž๊ฐ€ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ ํ™˜๊ฒฝ์—์„œ๋Š” [react-query๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ๋‹ค](https://tkdodo.eu/blog/you-might-not-need-react-query)๊ณ  ์–ธ๊ธ‰ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +์šฐ๋ฆฌ๋Š” Next.js์™€ RSC์˜ ๋™์ž‘์„ ๊ฐ€๋Šฅํ•œ ํ•œ ํฌํฌ(fork)ํ•˜์ง€ ์•Š์œผ๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด RSC ์ƒํƒœ๊ณ„์—์„œ ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์™€ ๋‹ค๋ฅธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ ๊ฐ„์˜ ์ฐจ์ด๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +๋˜ํ•œ, ๊ฐ€๊นŒ์šด ๋ฏธ๋ž˜์—๋Š” revalidatePath๋‚˜ revalidateTag๋ฅผ ์ˆ˜๋™์œผ๋กœ ํ˜ธ์ถœํ•  ํ•„์š”๊ฐ€ ์ค„์–ด๋“ค ๊ฒƒ์œผ๋กœ ๊ธฐ๋Œ€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ORMs/SDKs์™€ ๊ฐ™์€ ๋ฐ์ดํ„ฐ ํŽ˜์นญ ์†”๋ฃจ์…˜์ด ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฝ์ž…ํ•˜๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธํ•  ๋•Œ ๋ฌดํšจํ™”๋ฅผ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, Prisma์˜ SDK๊ฐ€ ์ž๋™์œผ๋กœ ์ด๋Ÿฌํ•œ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +```ts +// Without integration into the SDK +export async function updateUser(config) { + "use server"; + await prisma.user.update(config); + revalidateTag("prisma-user"); +} + +// With integration into the SDK +// If the SDK called revalidateTag and applied tags instead: +export async function updateUser(config) { + "use server"; + // Automatically calls `revalidateTag` on your behalf. + await prisma.user.update(config); +} +``` + +์ด ์ถ”๊ฐ€์ ์ธ ๋งฅ๋ฝ๊ณผ ๋‚ด๋ถ€ ์ž‘๋™ ๋ฐฉ์‹์— ๋Œ€ํ•œ ์„ค๋ช…, ๋ณ€๋™ ์‚ฌํ•ญ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•, stale-while-revalidate ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹ ์„ ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•, ๊ทธ๋ฆฌ๊ณ  ๋ฒ„๊ทธ ์ˆ˜์ • ์‚ฌํ•ญ์„ ๊ณ ๋ คํ–ˆ์„ ๋•Œ, ์ด ์„ค๋ช…์ด ์ดํ•ด๊ฐ€ ๋˜์‹œ๋‚˜์š”? ์•„๋‹ˆ๋ฉด ์—ฌ์ „ํžˆ ํ•ด๊ฒฐ๋˜์ง€ ์•Š์€ ์‚ฌ๋ก€๊ฐ€ ์žˆ๋‚˜์š”? ์‹œ๊ฐ„์„ 30์ดˆ๊ฐ€ ์•„๋‹Œ 0์ดˆ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ๋น„๋ก ๋’ค๋กœ ๋„ค๋น„๊ฒŒ์ด์…˜์—๋Š” ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๊ฒ ์ง€๋งŒ, ํ˜„์žฌ ์ง๋ฉดํ•˜๊ณ  ์žˆ๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”? ์•Œ๋ ค์ฃผ์„ธ์š”! + +๊ฒŒ์‹œ๋ฌผ์˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„๋ถ€ํ„ฐ ์ฝ๊ธฐ ์‹œ์ž‘ํ•˜์…จ๋‹ค๋ฉด, ์ด ํ† ๋ก ์— ๋Œ€ํ•ด ์˜๊ฒฌ์„ ๋‚จ๊ธฐ๊ธฐ ์ „์— ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ์ „์ฒด ์„ค๋ช…์„ ์ฝ์–ด๋ณด์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.