๐Ÿš€ Next.js 6์žฅ: Data Fetching ๋ฅด๋„ค์ƒ์Šค โ€” fetch ๋–ก์ƒ๊ณผ Memoization์˜ ๋น„๋ฐ€

๐Ÿ“‹ ๊ฐœ์š”

fetch ๋ฉ”๋ชจ์ด์ œ์ด์…˜๊ณผ RSC์—์„œ์˜ ๋ฐ์ดํ„ฐ ํŒจ์นญ ํŒจํ„ด, waterfall ๋ฐฉ์ง€ ์ „๋žต์„ ๋ฐฐ์›๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


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

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

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„
๊ฐ€๋กœ์ฑˆ fetch ์˜ ์ •์ฒด โ†’ ์บ์‹ฑ 1๋‹จ๊ณ„(Memoization) ๋ฉ”์ปค๋‹ˆ์ฆ˜ โ†’ DB ์ฟผ๋ฆฌ ์ค‘๋ณต ๋ฐฉ์ง€ ํŒจํ„ด

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

  • Props Drilling์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ฐ™์€ API๋ฅผ ์ค‘๋ณต ํ˜ธ์ถœํ•˜๋Š” ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋‹น๋‹นํ•˜๊ฒŒ ์งค ์ˆ˜ ์žˆ๋‹ค.
  • Next.js๊ฐ€ ๋ฌดํšจํ™” ๋ถˆ๊ฐ€๋Šฅํ•˜๊ฒŒ ๊ฐ•์ œ๋กœ ๊ฑธ์–ด๋ฒ„๋ฆฌ๋Š” 1๋‹จ๊ณ„ ๋‹จ๋ฐœ์„ฑ ์บ์‹œ(Memoization) ์˜ ์ˆ˜๋ช…์„ ์ •ํ™•ํžˆ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
  • Prisma, Drizzle ๋“ฑ fetch ๋ฅผ ์•ˆ ์“ฐ๋Š” ORM ํ™˜๊ฒฝ์—์„œ๋„ ์ค‘๋ณต ์ฟผ๋ฆฌ๋ฅผ ๋ฐฉ์–ดํ•˜๋Š” cache() ๋ž˜ํ•‘์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

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

  • ์˜์ฒ (์ƒˆ๋กœ ์˜จ ์ฃผ๋‹ˆ์–ด): "๋ฆฌ๋“œ ๋‹˜... ๋ ˆ์ด์•„์›ƒ์—์„œ๋„ ์œ ์ € ์ •๋ณด๊ฐ€ ํ•„์š”ํ•˜๊ณ  ๋ณธ๋ฌธ์—์„œ๋„ ํ•„์š”ํ•ด์„œ fetch ๋ฅผ ๋‘ ๋ฒˆ ์ผ๊ฑฐ๋“ ์š”. ๊ทธ๋Ÿฐ๋ฐ ๋กœ๊ทธ๋ฅผ ๋ณด๋‹ˆ DB ์กฐํšŒ๊ฐ€ ๋‘ ๋ฒˆ์”ฉ ๋ฐœ์ƒํ•ด์„œ ์„œ๋ฒ„ ๋น„์šฉ์ด ๋‘ ๋ฐฐ๋กœ ๊นจ์ง€๊ณ  ์žˆ์–ด์š”! ์ „์—ญ ์ƒํƒœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ผ๋„ ์จ์•ผ ํ• ๊นŒ์š”? ๐Ÿ˜ญ"
  • ์˜ํ˜ธ(FE ๋ฆฌ๋“œ): "์˜์ฒ  ๋‹˜... App Router ํ™˜๊ฒฝ์—์„œ๋Š” Next.js๊ฐ€ ๋ชฐ๋ž˜ fetch ํ•จ์ˆ˜๋ฅผ ๊ฐ€๋กœ์ฑ„์„œ ๋˜‘๊ฐ™์€ ์š”์ฒญ์„ ํ•˜๋‚˜๋กœ ๋ณ‘ํ•ฉ(Memoization) ํ•ด์ค˜์š”! ๋ฌด๊ฑฐ์šด ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ์ž๋Š” ๋” ์ด์ƒ ๋ฐ์ดํ„ฐ ๊ณต์œ ์šฉ์œผ๋กœ ์“ฐ์ง€ ์•Š์•„๋„ ๋œ๋‹ค๊ณ ์š”!"

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

์˜ค๋ž˜๋œ ๋ฆฌ์•กํŠธ ๋ฐฉ์‹์—์„œ๋Š” ์ปดํฌ๋„ŒํŠธ A์™€ ์ €์–ด์–ด ๋ฐ‘์— ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ Z๊ฐ€ ๋‘˜ ๋‹ค "ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์œ ์ € ์ •๋ณด"๋ฅผ ๋ณด๊ณ  ์‹ถ์„ ๋•Œ ์ฐธ ๊ดด๋กœ์› ์ง€.

  1. ์ตœ์ƒ๋‹จ์—์„œ API๋ฅผ ํ•œ ๋ฒˆ ํ˜ธ์ถœํ•ด Props Drilling์œผ๋กœ ์•„๋ž˜ ์ปดํฌ๋„ŒํŠธ๊นŒ์ง€ ๋‚ด๋ ค์ฃผ๋“ ๊ฐ€,
  2. Redux, Recoil, React Query (Tanstack Query) ๊ฐ™์€ ์ „์—ญ(Global) ํด๋ผ์ด์–ธํŠธ ์บ์‹ฑ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ถ™์ด๋“ ๊ฐ€.

ํ•˜์ง€๋งŒ ์ด ๋ฐฉ์‹๋“ค์€ ๋ฐฉ๋Œ€ํ•œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ฒˆ๋“ค์„ ๋ธŒ๋ผ์šฐ์ €์— ๋‚ด๋ ค๋ณด๋‚ด๊ณ  use client ์ตœ์ƒ๋‹จ ์˜ค์—ผ์„ ์ „ํŒŒํ•˜๋Š” ๊ฑฐ๋Œ€ํ•œ ๋ณ‘๋ชฉ์ด์—ˆ์–ด.
Next.js 13+ App Router ์„ธ์ƒ์—์„œ๋Š” ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋Œ€์„ธ๊ฐ€ ๋˜์—ˆ์–ด. ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋ผ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ?
๋†€๋ž๊ฒŒ๋„ ๋ฆฌ์•กํŠธ์™€ ๋„ฅ์ŠคํŠธ ํŒ€์ด ์ œ์‹œํ•œ ํ•ด๋‹ต์€ "๊ทธ๋ƒฅ ์—ฌ๋Ÿฌ ๊ตฐ๋ฐ์—์„œ ๋˜‘๊ฐ™์€ API๋ฅผ ๊ฐ์ž ํ˜ธ์ถœํ•˜์„ธ์š”! ์ค‘๋ณต ์ œ๊ฑฐ๋Š” ์šฐ๋ฆฌ๊ฐ€ ๋ชฐ๋ž˜ ์•Œ์•„์„œ ํ•ด์ค„๊ฒŒ์š”." ์˜€์–ด. ์ด ๋งˆ์ˆ  ๊ฐ™์€ "๋‹จ๋ฐœ์„ฑ ์บ์‹œ(Request Memoization)" ์›๋ฆฌ๋ฅผ ๋ชจ๋ฅด๋ฉด ์˜›๋‚  ๋ฐฉ์‹์œผ๋กœ ๋Œ์•„๊ฐ€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์„œ๋ฒ„ ๋ถ€ํ•˜์™€ ํด๋ผ์ด์–ธํŠธ ๋ฒˆ๋“ค์„ ๋‚ญ๋น„ํ•˜๊ฒŒ ๋ผ.


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

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด? (์˜์ˆ˜๋„ค ๊ตญ๋ฐฅ์ง‘ ๊ณ ๊ธฐ ๊ณ ๋ช…)

์†๋‹˜(์‚ฌ์šฉ์ž) ์ด ๋“ค์–ด์™€์„œ "์ˆ˜์œก ๊ตญ๋ฐฅ ํ•˜๋‚˜์š”!" ๋ผ๊ณ  ์ฃผ๋ฌธ์„ ํ–ˆ์–ด.(๋‹จ์ผ ๋ Œ๋”๋ง ์š”์ฒญ)

์ฃผ๋ฐฉ ๋ณด์กฐ A(๋ ˆ์ด์•„์›ƒ) ๊ฐ€ "์–ด! ์ˆ˜์œก ๊ณ ๊ธฐ ํ•œ ์ ‘์‹œ ํ•„์š”ํ•ด!" ๋ผ๊ณ  ์†Œ๋ฆฌ์น˜๋ฉฐ ๋ƒ‰์žฅ๊ณ (fetch) ๋กœ ๋›ฐ์–ด๊ฐ”์–ด. ์ ‘์‹œ๋ฅผ ๊บผ๋‚ด์™€.
1์ดˆ ๋’ค์— ์ฃผ๋ฐฉ ๋ณด์กฐ B(๋ณธ๋ฌธ) ๋„ ๋˜‘๊ฐ™์ด "์–ด! ๋‚˜๋„ ์ˆ˜์œก ํ•œ ์ ‘์‹œ ํ•„์š”ํ•œ๋ฐ!" ๋ผ๋ฉฐ ๋ƒ‰์žฅ๊ณ (fetch) ๋กœ ๋›ฐ์–ด๊ฐ”์ง€.

๊ทธ๋Ÿฐ๋ฐ ์ฃผ๋ฐฉ์žฅ(Next.js) ์ด ์ฒ™ ๊ฐ€๋กœ๋ง‰์œผ๋ฉฐ ๋งํ•ด.
"์–ดํ—ˆ! ๋ฐฉ๊ธˆ A๊ฐ€ ๊บผ๋‚ธ ๊ณ ๊ธฐ ์ ‘์‹œ, ๋‚ด๊ฐ€ ์—ฌ๊ธฐ ๋„๋งˆ ์œ„์—(์ž„์‹œ ๋ฉ”๋ชจ๋ฆฌ) ๋˜‘๊ฐ™์ด ์ฐ์–ด๋†จ์–ด! ๋ƒ‰์žฅ๊ณ  ๋ฌธ ๋˜ ์—ด์ง€ ๋ง๊ณ  ์ด๊ฑฐ ๊ทธ๋ƒฅ ๋‚˜๋ˆ ์„œ ๋‹ด์•„!"

๋‹จ, ์†๋‹˜์ด ๊ตญ๋ฐฅ์„ ๋‹ค ๋จน๊ณ  ๋‚˜๊ฐ€๋Š” ์ˆœ๊ฐ„(๋ Œ๋”๋ง ์™„๋ฃŒ), ๋„๋งˆ ์œ„์— ๋‚จ์€ ๊ณ ๊ธฐ ์ •๋ณด๋Š” ๊นจ๋—์ด ์น˜์›Œ์ ธ์„œ ๋ฆฌ์…‹ ๋ผ. ๋‹ค์Œ ์†๋‹˜์ด ์˜ค๋ฉด ๋˜ ์ƒˆ๋กœ ๊บผ๋‚ด์•ผ ํ•˜์ง€. ์ด๊ฒŒ Request Memoization ์ด์•ผ.


๐Ÿงฉ fetch API์˜ ๋ถ€ํ™œ: Next.js๊ฐ€ ํ›”์นœ ์›๋ž˜ ํ‘œ์ค€ ๐ŸŸข

๊ธฐ์กด ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋“ค์€ axios๋ฅผ ์„ ํ˜ธํ–ˆ์–ด.
ํ•˜์ง€๋งŒ Next.js App Router ์ƒํƒœ๊ณ„์—์„œ๋Š” ์ˆœ์ˆ˜ ๋ธŒ๋ผ์šฐ์ € API์˜€๋˜ fetch๋ฅผ ๋ฌด์กฐ๊ฑด 1์ˆœ์œ„๋กœ ์จ์•ผ ํ•ด.

์™œ๋ƒ๊ณ ? Next.js ์ฝ”์–ด ํŒ€์ด ๋ธŒ๋ผ์šฐ์ €์˜ ์ „์œ ๋ฌผ์ด์—ˆ๋˜ fetch ํ•จ์ˆ˜๋ฅผ Node.js ํ™˜๊ฒฝ์—์„œ ๋Œ์•„๊ฐˆ ์ˆ˜ ์žˆ๋„๋ก ๋ชฐ๋ž˜ ๋ฎ์–ด์”Œ์›Œ์„œ(Monkey Patching) ํ™•์žฅ ๊ฐœ์กฐํ•ด๋†จ๊ฑฐ๋“ .

์ด ๊ฐœ์กฐ๋œ fetch๋Š” ๋‹จ์ˆœํ•œ ํ†ต์‹  ๋ชจ๋“ˆ์„ ๋„˜์–ด์„œ ์„œ๋ฒ„ ์บ์‹œ ์ €์žฅ์†Œ(next: { revalidate }, cache: 'force-cache')์™€ ์—ฐ๊ฒฐ๋œ ๊ฑฐ๋Œ€ํ•œ ์บ์‹œ ์ปจํŠธ๋กค๋Ÿฌ์•ผ. axios๋Š” ์ˆœ์ˆ˜ ์„œ๋“œํŒŒํ‹ฐ์ด๋ฏ€๋กœ ์ด ๋งˆ๋ฒ•์˜ ํ˜œํƒ์„ 1๋„ ๋ณผ ์ˆ˜๊ฐ€ ์—†์–ด! (๋‹จ๋ฐœ์„ฑ ์ค‘๋ณต ์ œ๊ฑฐ๋Š” ์—ฌ์ „ํžˆ ์Šค์Šค๋กœ ํ•ด์•ผ ํ•จ)


๐Ÿ›ก๏ธ Request Memoization โ€” ๋˜‘๊ฐ™์€ API ํ˜ธ์ถœ์˜ ์ค‘๋ณต ๋ฐฉ์–ด ๐ŸŸก

๐Ÿ’ก ์ด ๋ฌธ์„œ์—์„œ ๋‹ค๋ฃจ๋Š” ์บ์‹ฑ์€ ๋‹จ ํ•˜๋‚˜์˜ ์‚ฌ์šฉ์ž ์ ‘์†(1 Request) ์ฃผ๊ธฐ ๋™์•ˆ๋งŒ ์‚ด์•„๋‚จ๋Š” ๋‹จ๋ฐœ์„ฑ "๊ธฐ์–ต(Memoization)" ์ด๋‹ค! ์˜๊ตฌ ๋ณด์กด๋˜๋Š” ๋ฐ์ดํ„ฐ ์บ์‹œ ์ฑ•ํ„ฐ๋Š” [Advanced]์—์„œ ๋”ฐ๋กœ ๋”ฅ๋‹ค์ด๋ธŒ ํ•  ์˜ˆ์ •.

์ปดํฌ๋„ŒํŠธ ๊ตฌ์กฐ

// ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ํŽ˜์ด์ง€ ๊ตฌ์กฐ
app/
 โ”œโ”€ layout.tsx (์‚ฌ์ด๋“œ๋ฐ” ๋ฉ”๋‰ด - ์ ‘์† ์œ ์ € ํ”„๋กœํ•„ ํ•„์š”)
 โ””โ”€ user/[id]/page.tsx (๋ณธ๋ฌธ - ์—ญ์‹œ๋‚˜ ์ ‘์† ์œ ์ € ํ”„๋กœํ•„ ํ•„์š”)

โŒ ๊ณผ๊ฑฐ์˜ ๊ณต๋ฃก ๋ฐฉ์‹ (์ „์—ญ ์Šคํ† ์–ด)
๋ ˆ์ด์•„์›ƒ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜จ ๋’ค Context API๋‚˜ Redux/Zustand Store์— ์–ต์ง€๋กœ ๋ฐ€์–ด ๋„ฃ๊ณ  page.tsx์—์„œ ๊บผ๋‚ด ์“ฐ๊ฒŒ ํ•˜๋Š” ๋ฐฉ์‹. ์ฝ”๋“œ๊ฐ€ ์ง€์ €๋ถ„ํ•ด์ง€๊ณ  ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธํ™”๊ฐ€ ๊ฐ•์ œ๋œ๋‹ค.

โœ… RSC ์ตœ์‹  ํผ (๊ฐ์ž๋„์ƒ ๋ Œ๋”๋ง)

// 1. app/layout.tsx
// ํ—ค๋”์— ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž "์˜์ฒ "์˜ ์ด๋ฆ„์„ ๋„์›Œ์•ผ ํ•œ๋‹ค!
export default async function Layout({ children }) {
  const res = await fetch('https://api.my.com/user/1');
  const user = await res.json(); // { name: "์˜์ฒ ", job: "ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž" }
 
  return <nav>ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค, {user.name} ๋‹˜</nav>{children}
}
// 2. app/user/[id]/page.tsx
// ๋ณธ๋ฌธ์—์„œ๋„ ํ”„๋กœํ•„ ์ˆ˜์ • ํผ์„ ์œ„ํ•ด ๋˜ ๋‚ด ์ •๋ณด ์ „์ฒด๊ฐ€ ํ•„์š”ํ•˜๋‹ค!
export default async function Page() {
  const res = await fetch('https://api.my.com/user/1'); // URL๊ณผ ๋ฉ”์†Œ๋“œ๊ฐ€ 100% ๋˜‘๊ฐ™๋‹ค!
  const user = await res.json();
 
  return <div>๋‚ด ์ง์—…: {user.job} </div>
}

[๋‚ด๋ถ€์—์„œ ์ผ์–ด๋‚˜๋Š” ์ผ]

  1. ์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€ ์ ‘์†. (๋ Œ๋”๋ง ํŠธ๋ฆฌ ํƒ์ƒ‰ ์‹œ์ž‘)
  2. layout.tsx ์‹คํ–‰ ์ค‘ fetch('/user/1') ์กฐ์šฐ. Next.js๊ฐ€ "๋‚ด ๋‹จ๋ฐœ์„ฑ ๋ฉ”๋ชจ๋ฆฌ์— ์ด ์š”์ฒญ๊ฒฐ๊ณผ ์žˆ๋‚˜? ์—†๋„ค. ์ง„์งœ DB ๊ฐ€์„œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜จ๋‹ค(MISS)."
  3. ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜จ ๋’ค, ์‘๋‹ต ๊ฐ์ฒด๋ฅผ ์ปดํฌ๋„ŒํŠธ์— ๋„˜๊น€๊ณผ ๋™์‹œ์— Next.js ๋‚ด๋ถ€ ๋ฉ”๋ชจ๋ฆฌ(์บ์‹œ ํŠธ๋ฆฌ)์— ์‚ด์ง ์ €์žฅํ•ด๋‘ .
  4. ํŠธ๋ฆฌ ์•„๋ž˜์ชฝ page.tsx ์‹คํ–‰. ๋˜ fetch('/user/1') ์กฐ์šฐ.
  5. Next.js๊ฐ€ ์›ƒ์œผ๋ฉด์„œ ๊ฐ€๋กœ์ฑ”. "์•„ํ•˜! ์ด URL, ์•„๊นŒ 1๋ฐ€๋ฆฌ์ดˆ ์ „์— ์œ„์—์„œ ๋˜‘๊ฐ™์ด ๋ถˆ๋ €๋˜ ๊ฑฐ์ž–์•„! ์ง„์งœ ์„œ๋ฒ„๋กœ ๋„คํŠธ์›Œํฌ ์ „์†กํ•˜์ง€ ๋ง๊ณ  ์•„๊นŒ ์ €์žฅํ•ด๋‘” ๊ฑฐ ๋ฐ”๋กœ ๋Œ๋ ค์ค„๊ฒŒ (HIT)."
  6. ๋ Œ๋”๋ง ์ข…๋ฃŒ ํ›„ HTML ์™„์„ฑ. ์ด ๋ฉ”๋ชจ์ด์ œ์ด์…˜ ์ˆ˜์ฒฉ์€ ์ฆ‰์‹œ ํ๊ธฐ ๋จ. (์„œ๋ฒ„ ๋ฆฌ์†Œ์Šค ๋ฐ˜ํ™˜)

์–ด๋•Œ? ์šฐ๋ฆฌ๋Š” ์•„๋ฌด๋Ÿฐ ์ „์—ญ ์ƒํƒœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(Zustand ๋“ฑ)๋ฅผ ๊น”์ง€ ์•Š๊ณ ๋„, ๊ทธ์ € ๋˜‘๊ฐ™์€ ์ฃผ์†Œ์˜ fetch๋ฅผ ์—ฌ๋Ÿฌ ์ค„ ๋ณต๋ถ™ํ–ˆ์„ ๋ฟ์ธ๋ฐ ์„œ๋ฒ„ ๋ถ€ํ•˜๋Š” ๋ฌด์กฐ๊ฑด 1ํšŒ๋กœ ํ•œ์ •๋˜๋Š” ์ถ•๋ณต์„ ๋ˆ„๋ฆฌ๊ฒŒ ๋œ ๊ฑฐ์•ผ. Props Drilling์—์„œ ์˜์›ํžˆ ํ•ด๋ฐฉ๋œ ๊ฑฐ์ง€!


๐ŸŒฑ fetch๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•œ ํ™˜๊ฒฝ: ORM๊ณผ ์„œ๋ฒ„ SDK ์šฐํšŒ๋ฒ• ๐Ÿ”ด

์˜์ฒ (์‹ ์ž…)์ด๊ฐ€ ํ—๋ ˆ๋ฒŒ๋–ก ๋›ฐ์–ด์™€. "๋ฆฌ๋“œ๋‹˜!! ํฐ์ผ ๋‚ฌ์–ด์š”! ์ €ํฌ๋Š” ํ”„๋ก ํŠธ ๋‹จ๋… fetch๋กœ API ํ˜ธ์ถœ ์•ˆ ํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ง์ ‘ ์ฐŒ๋ฅด๋Š” Prisma๋ฅผ ์“ฐ๊ณ  ์žˆ์–ด์„œ db.user.findUnique() ํ•จ์ˆ˜๋ฅผ ์“ฐ์ž–์•„์š”! ์–˜๋Š” fetch๊ฐ€ ์•„๋‹ˆ๋ผ์„œ ๋ ˆ์ด์•„์›ƒ์ด๋ž‘ ๋ณธ๋ฌธ์— ๋‘ ๋ฒˆ ์“ฐ๋ฉด DB ์ฟผ๋ฆฌ๊ฐ€ ์ง„์งœ๋กœ ๋‘ ๋ฒˆ ๋‚˜๊ฐ€์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ถ€ํ•˜๊ฐ€ ๋‘ ๋ฐฐ๋กœ ๋Š˜์–ด๋‚˜์š”!"

์˜ํ˜ธ(๋ฆฌ๋“œ)๊ฐ€ ๋Œ€๋‹ตํ•ด. "๋„ค, Next.js ์ฝ”์–ด ํŒ€์ด ๊ฐ€๋กœ์ฑ„์„œ ๋งˆ๊ฐœ์กฐํ•œ ๊ฑด ์˜ค์ง fetch ๋ฟ์ด์—์š”. ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ Prisma, Drizzle, Firebase SDK, Stripe ๋“ฑ์€ ๋ฐ”๋ณด์ฒ˜๋Ÿผ ๋งค๋ฒˆ ์ •์งํ•˜๊ฒŒ ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆฌ์ฃ . ์ด๋Ÿด ๋• ์šฐ๋ฆฌ๊ฐ€ ์ˆ˜๋™์œผ๋กœ React.cache() ๋ผ๋Š” ๋ฐฉ์–ด๋ง‰์„ ์ง์ ‘ ์น˜๋Š” ๊ฒ๋‹ˆ๋‹ค."

โœ… ์™„๋ฒฝํ•œ ์ˆ˜๋™ ๋ž˜ํ•‘ (React cache API ๋„์ž…)

fetch๋ฅผ ๋ชป ์“ด๋‹ค๋ฉด, ๋‚ด ์ผ๋ฐ˜ ํ•จ์ˆ˜๋ผ๋„ ์ € 1ํšŒ์šฉ ๋‹จ๋ฐœ์„ฑ ๋ฉ”๋ชจ๋ฆฌ(Memoization)์˜ ์ถ•๋ณต์„ ๋ฐ›๊ฒŒ๋” ๋ชจ์ž(cache)๋ฅผ ์”Œ์›Œ์ฃผ๋ฉด ๋ผ.

// app/lib/getUser.ts (๐Ÿ”ฅ ์ค‘์š” ์œ ํ‹ธ ํ•จ์ˆ˜ ํŒŒ์ผ)
import { cache } from 'react' // Next.js๊ฐ€ ์•„๋‹ˆ๋ผ React ์ฝ”์–ด์—์„œ ์ง€์›ํ•˜๋Š” API!
import db from './prismaClient'
 
// ์ˆœ์ • ํ•จ์ˆ˜์ธ getUserById๋ฅผ cache() ๋ฐฉ์–ด๋ง‰์œผ๋กœ ๊ฐ์‹ผ๋‹ค!
export const getUserById = cache(async (id: string) => {
  console.log('์ด ๋กœ๊ทธ๊ฐ€ ๋”ฑ ํ•œ ๋ฒˆ๋งŒ ์ฐํ˜€์•ผ ์ •์ƒ์ž…๋‹ˆ๋‹ค!'); // ์ฆ๋ช…์šฉ ๋กœ๊ทธ
  const user = await db.user.findUnique({ where: { id } });
  return user;
})

์ด์ œ layout.tsx์™€ page.tsx์—์„œ ์ด getUserById('123') ํ•จ์ˆ˜๋ฅผ 10๋ฒˆ, 100๋ฒˆ ๋ณต๋ถ™ํ•ด์„œ ๋งˆ๊ตฌ์žก์ด๋กœ ํ˜ธ์ถœํ•ด๋„, ๋™์ผํ•œ ์ธ์ž(id='123')์— ๋Œ€ํ•œ ๋ฆฌ์•กํŠธ ์‚ฌ์ดํด ๋‚ด์—์„œ์˜ DB ์ฟผ๋ฆฌ๋Š” ์ฝ˜์†” ๋ณด์žฅ ๋‹จ 1๋ฒˆ๋งŒ ๋ฐœ๋™ํ•ด.

๐Ÿ’ก ์‹ฌํ™” ์ฃผ์˜: ์ด React.cache()๋Š” Next.js์˜ 4์ค‘ ์บ์‹œ ์ค‘ ์ตœ๊ณ ๊ธ‰์ธ Data Cache(์˜๊ตฌ ๋ณด๊ด€ ์บ์‹œ)๊นŒ์ง€๋Š” ๊ฑด๋“œ๋ฆฌ์ง€ ๋ชปํ•ด! ์˜ค์ง '์ด๋ฒˆ ํ•œ ๋ฒˆ์˜ ํŽ˜์ด์ง€ ๋ฐฉ๋ฌธ(1 Request Lifecycle)'์— ํ•œํ•ด์„œ๋งŒ ์ค‘๋ณต์„ ๋ง‰๋Š” ๋˜‘๋˜‘ํ•œ ์ง€์šฐ๊ฐœ๊ฐ€ ๋‹ฌ๋ฆฐ ์ˆ˜์ฒฉ์ผ ๋ฟ์ด์•ผ.


๐Ÿ”‘ API ์ •๋ณด ๋“œ๋ฆด๋ง ๋ฐฉ์ง€ โ€” ์š”์ฒญ ์ปจํ…์ŠคํŠธ ์ง์ ‘ ์ ‘๊ทผ ํŒจํ„ด ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • cookies() / headers() ๋ฅผ ์–ด๋А ๊นŠ์ด์˜ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ๋“  ์ง์ ‘ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Œ์„ ์•ˆ๋‹ค
  • ์ธ์ฆ ํ† ํฐ์„ Props๋กœ ๋‚ด๋ ค๋ฐ›์ง€ ์•Š์•„๋„ ๋˜๋Š” ์ด์œ ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค
  • React.cache() ๊ฐ€ '์ด๋ฒˆ ์š”์ฒญ(1 Request Lifecycle)' ๋ฒ”์œ„์—์„œ๋งŒ ์œ ํšจํ•จ์„ ์•Œ๊ณ  ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค

๋ฌธ์ œ: "์ธ์ฆ ์ •๋ณด๋„ ๊ฒฐ๊ตญ Prop Drilling์ด ์ƒ๊ธฐ์ž–์•„์š”!"

์˜์ฒ ์ด๊ฐ€ ๋˜ ๋›ฐ์–ด์™€. "๋ฆฌ๋“œ๋‹˜! fetch('/api/posts')๋ฅผ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ฐ์ž ํ˜ธ์ถœํ•˜๋Š” ๊ฑด ์ด์ œ ์•Œ๊ฒ ์–ด์š”. ๊ทธ๋Ÿฐ๋ฐ... ๊ทธ API๊ฐ€ ์ธ์ฆ์ด ํ•„์š”ํ•ด์„œ Authorization: Bearer <ํ† ํฐ> ํ—ค๋”๋ฅผ ๋ถ™์—ฌ์•ผ ํ•œ๋‹ค๋ฉด์š”? ํ† ํฐ์€ ์ฟ ํ‚ค์—์„œ ๊บผ๋‚ด์•ผ ํ•˜๋Š”๋ฐ, ์ตœ์ƒ๋‹จ ๋ ˆ์ด์•„์›ƒ์—์„œ ๊บผ๋‚ธ ํ† ํฐ์„ ํŽ˜์ด์ง€๋กœ Props๋กœ ๋‚ด๋ ค์ฃผ๊ณ , ๋˜ ๊ทธ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋กœ Prop Drillingํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ ์•„๋‹Œ๊ฐ€์š”? ์•„, ๊ทธ๋ฆฌ๊ณ  URL ํŒŒ๋ผ๋ฏธํ„ฐ๋‚˜ ์ฟผ๋ฆฌ์ŠคํŠธ๋ง๋„ ๋งˆ์ฐฌ๊ฐ€์ง€ ์•„๋‹Œ๊ฐ€์š”? /posts/[id] ์˜ id ๋ฅผ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊นŒ์ง€ ๋‚ด๋ ค์ค˜์•ผ ํ•˜์ž–์•„์š”?"

์˜ํ˜ธ๊ฐ€ ์›ƒ์œผ๋ฉฐ ๋Œ€๋‹ตํ•ด. "์ฟ ํ‚ค/ํ—ค๋”๋“ , params๋“ , ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ชจ๋‘ ์–ด๋А ๊นŠ์ด์—์„œ๋“  ์ง์ ‘ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•ด์š”. next/headers์—์„œ ์ œ๊ณตํ•˜๋Š” cookies()์™€ headers() ํ•จ์ˆ˜, ๊ทธ๋ฆฌ๊ณ  params์™€ searchParams ๋Š” ๋ Œ๋” ํŠธ๋ฆฌ ์–ด๋А ๊นŠ์ด์—์„œ๋“  ์ง์ ‘ ๋ฐ›์•„ ์“ธ ์ˆ˜ ์žˆ๊ฑฐ๋“ ์š”. ์„œ๋ฒ„์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋Šฅํ•œ ํŠน๊ถŒ์ด์—์š”!"

โŒ ๊ณผ๊ฑฐ ๋ฐฉ์‹ (์ฟ ํ‚ค Prop Drilling)

// app/layout.tsx โ€” ์ฟ ํ‚ค ๊บผ๋‚ด์„œ ์•„๋ž˜๋กœ ๋‚ด๋ ค์คŒ
export default async function Layout({ children }) {
  const cookieStore = await cookies()
  const token = cookieStore.get('session-token')?.value
  return <Main token={token}>{children}</Main> // token์„ props๋กœ ๋„˜๊ฒจ์•ผ ํ•จ
}
 
// app/page.tsx โ€” ๋ฐ›์•„์„œ ๋˜ ๋‚ด๋ ค์คŒ
export default function Page({ token }) {
  return <PostList token={token} /> // ๊ณ„์† ๋‚ด๋ ค๊ฐ€์•ผ ํ•จ
}

โœ… RSC ๋ฐฉ์‹ (์–ด๋””์„œ๋“  ์ง์ ‘ ์ ‘๊ทผ)

// app/posts/PostList.tsx โ€” ์–ด๋А ๊นŠ์ด์— ์žˆ์–ด๋„ ์ง์ ‘ ์ฟ ํ‚ค ์ ‘๊ทผ!
import { cookies } from 'next/headers'
 
export default async function PostList() {
  const cookieStore = await cookies()
  const token = cookieStore.get('session-token')?.value
 
  const posts = await fetch('https://api.my.com/posts', {
    headers: { Authorization: `Bearer ${token}` }, // Props ์—†์ด ์ง์ ‘ ์‚ฌ์šฉ
  })
  const data = await posts.json()
  return <ul>{data.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}

Props ๋“œ๋ฆด๋ง์ด ์™„์ „ํžˆ ์‚ฌ๋ผ์กŒ์–ด. cookies()์™€ headers() ๋Š” HTTP ์š”์ฒญ ๊ฐ์ฒด์— ์ง์ ‘ ์ ‘๊ทผํ•˜๋Š” ์„œ๋ฒ„ ์ „์šฉ ํ•จ์ˆ˜์•ผ. Node.js ์„œ๋ฒ„ ์•ˆ์—์„œ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ์–ด๋А ๊นŠ์ด์˜ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ ํ˜ธ์ถœํ•ด๋„ ๋™์ผํ•œ ํ˜„์žฌ ์š”์ฒญ ์ปจํ…์ŠคํŠธ๋ฅผ ์ฐธ์กฐํ•ด.

params์™€ searchParams๋„ ์ง์ ‘ ๋ฐ›์œผ๋ฉด ๋ผ

์ฟ ํ‚ค๋งŒ์ด ์•„๋‹ˆ์•ผ. URL ๋™์  ํŒŒ๋ผ๋ฏธํ„ฐ(params)์™€ ์ฟผ๋ฆฌ์ŠคํŠธ๋ง(searchParams)๋„ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ง์ ‘ props๋กœ ๋ฐ›๊ฑฐ๋‚˜, Next.js 15 ์ด์ƒ์—์„œ๋Š” await ํ˜•ํƒœ๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด.

// app/posts/[id]/CommentSection.tsx
// โœ… page.tsx๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๊ณ  ์ง์ ‘ params๋ฅผ ๋ฐ›์•„์„œ ์“ธ ์ˆ˜ ์žˆ์–ด
export default async function CommentSection({
  params,
}: {
  params: Promise<{ id: string }>  // Next.js 15 ๊ธฐ์ค€ Promise ํƒ€์ž…
}) {
  const { id } = await params  // URL: /posts/123 โ†’ id = "123"
 
  const comments = await fetch(`https://api.my.com/posts/${id}/comments`)
  return <ul>...</ul>
}
// app/search/ResultList.tsx
// โœ… ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ(?q=react&page=2)๋„ ์ง์ ‘ ๋ฐ›์•„์„œ ์“ธ ์ˆ˜ ์žˆ์–ด
export default async function ResultList({
  searchParams,
}: {
  searchParams: Promise<{ q?: string; page?: string }>
}) {
  const { q, page } = await searchParams  // ์ฟผ๋ฆฌ์ŠคํŠธ๋ง ์ง์ ‘ ์ ‘๊ทผ
 
  const results = await fetch(`https://api.my.com/search?q=${q}&page=${page}`)
  return <ul>...</ul>
}

๐Ÿ’ก Next.js 15 ์ฃผ์˜์‚ฌํ•ญ: params์™€ searchParams ๊ฐ€ ๋น„๋™๊ธฐ(Promise) ํƒ€์ž…์œผ๋กœ ๋ฐ”๋€Œ์—ˆ์–ด. await๋ฅผ ๋ฐ˜๋“œ์‹œ ๋ถ™์—ฌ์•ผ ํ•ด. Next.js 14 ์ดํ•˜์—์„œ๋Š” params.id ์ฒ˜๋Ÿผ ๋ฐ”๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด.

React.cache()๋กœ ์„ธ์…˜ ์ •๋ณด 1ํšŒ๋งŒ ์กฐํšŒํ•˜๊ธฐ

์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ฟ ํ‚ค๋ฅผ ๊ฐ์ž ๊บผ๋‚ด API ์š”์ฒญ์„ ํ•œ๋‹ค๋ฉด, ๋งค๋ฒˆ ์ธ์ฆ API๊ฐ€ ๋”ฐ๋กœ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์–ด. React.cache()๋กœ ๊ฐ์‹ธ์„œ ์š”์ฒญ๋‹น 1ํšŒ๋งŒ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•ด๋ผ.

// app/lib/auth.ts
import { cache } from 'react'
import { cookies } from 'next/headers'
 
// ์ด ํ•จ์ˆ˜๋Š” ํ•œ ์š”์ฒญ ์‚ฌ์ดํด์—์„œ ๋ช‡ ๋ฒˆ์„ ํ˜ธ์ถœํ•ด๋„ ์‹ค์ œ fetch๋Š” ๋”ฑ 1๋ฒˆ๋งŒ ์‹คํ–‰๋ผ
export const getSession = cache(async () => {
  const cookieStore = await cookies()
  const token = cookieStore.get('session-token')?.value
  if (!token) return null
 
  const res = await fetch('https://api.my.com/me', {
    headers: { Authorization: `Bearer ${token}` },
  })
  return res.json() // { userId: 'abc123', name: '์˜์ฒ ', role: 'user' }
})

์ด์ œ layout.tsx, page.tsx, ์–ด๋–ค ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ๋“  getSession()์„ ํ˜ธ์ถœํ•ด๋„ ์‹ค์ œ HTTP ์š”์ฒญ์€ ๋‹จ 1๋ฒˆ๋งŒ ๋‚˜๊ฐ€.

// ๋ ˆ์ด์•„์›ƒ์—์„œ๋„ ํ˜ธ์ถœ
export default async function Layout({ children }) {
  const session = await getSession() // 1๋ฒˆ์งธ ํ˜ธ์ถœ โ†’ ์‹ค์ œ fetch ๋ฐœ๋™ (MISS)
  return <nav>์•ˆ๋…•ํ•˜์„ธ์š”, {session?.name} ๋‹˜</nav>
}
 
// ๊นŠ์€ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ๋„ ํ˜ธ์ถœ
export default async function UserCard() {
  const session = await getSession() // 2๋ฒˆ์งธ ํ˜ธ์ถœ โ†’ ์บ์‹œ์—์„œ ์ฆ‰์‹œ ๋ฐ˜ํ™˜ (HIT)
  return <div>์—ญํ• : {session?.role}</div>
}

โฑ๏ธ React.cache ์œ ํšจ ๋ฒ”์œ„ โ€” "์ด๋ฒˆ ์š”์ฒญ"์ด ์ „๋ถ€๋‹ค

โš ๏ธ ์ค‘์š”: React.cache() ์˜ ๋ฉ”๋ชจ์ด์ œ์ด์…˜์€ ๋‹จ์ผ ๋ Œ๋” ์‚ฌ์ดํด(1 HTTP Request) ๋ฒ”์œ„์—์„œ๋งŒ ์œ ํšจํ•ด. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘์†ํ•˜๋ฉด ์บ์‹œ๋Š” ์™„์ „ํžˆ ๋น„์›Œ์ง€๊ณ  ์ƒˆ๋กœ ์‹œ์ž‘ํ•ด.

์‚ฌ์šฉ์ž A์˜ ์š”์ฒญ ์‹œ์ž‘ โ†’ getSession() 1๋ฒˆ์งธ ํ˜ธ์ถœ (MISS) โ†’ ์‹ค์ œ API fetch ์‹คํ–‰
                    โ†’ getSession() 2๋ฒˆ์งธ ํ˜ธ์ถœ (HIT)  โ†’ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ฆ‰์‹œ ๋ฐ˜ํ™˜
์‚ฌ์šฉ์ž A์˜ ์‘๋‹ต ์™„๋ฃŒ โ†’ โŒ ์บ์‹œ ํ๊ธฐ๋จ
 
์‚ฌ์šฉ์ž B์˜ ์š”์ฒญ ์‹œ์ž‘ โ†’ getSession() 1๋ฒˆ์งธ ํ˜ธ์ถœ (MISS) โ†’ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ fetch

์ด ๋•๋ถ„์— ์‚ฌ์šฉ์ž ๊ฐ„ ๋ฐ์ดํ„ฐ ์˜ค์—ผ(Cross-user data leak) ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์•„. ์‚ฌ์šฉ์ž A์˜ ์„ธ์…˜์ด ์‚ฌ์šฉ์ž B์—๊ฒŒ ํ˜๋Ÿฌ๊ฐ€๋Š” ์ผ์€ ์ ˆ๋Œ€ ์—†์–ด.

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
RSC์—์„œ๋Š” ์ธ์ฆ ํ† ํฐ์„ props๋กœ ๋“ค๊ณ  ๋›ฐ์–ด๋‹ค๋‹ ํ•„์š” ์—†์–ด. next/headers์—์„œ ์ง์ ‘ ๊บผ๋‚ด๊ณ , React.cache() ๋กœ ๊ฐ์‹ธ๋ฉด ํ•œ ์š”์ฒญ ์•ˆ์—์„œ ์ค‘๋ณต ํ˜ธ์ถœ๋„ ๋ง‰์„ ์ˆ˜ ์žˆ์–ด. ๋‹จ, ์ด ์บ์‹œ๋Š” ํ˜„์žฌ ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์ด ๋๋‚˜๋Š” ์ˆœ๊ฐ„ ์ž๋™ ํ๊ธฐ๋ผ.

โžก๏ธ ๋‹ค์Œ ์„น์…˜์—์„œ๋Š”: ์ž์ฃผ ๋งŒ๋‚˜๋Š” ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋“ค์„ ์ฆ์ƒ๋ณ„๋กœ ๋ฌถ์€ ์นดํƒˆ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•ด๋ด.


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

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

โŒ You are using an unsupported version of Next.js for React.cache

์›์ธ: ๋ฌด์‹ฌ์ฝ” ๋‹ค๋ฅธ ํŒŒ์ผ์—์„œ cache ํ•จ์ˆ˜๋ฅผ ์ž˜๋ชป ์ผ๊ฑฐ๋‚˜ ํ™˜๊ฒฝ ์ด์Šˆ.
ํ•ด๊ฒฐ์ฑ…: cache๋Š” ์˜ค๋กœ์ง€ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ(Server Components) ์ƒํƒœ๊ณ„์™€ ์„œ๋ฒ„ ๋‹จ์—์„œ๋งŒ ๋™์ž‘ํ•˜๋Š” ๋ฉ”๋ชจ์ด์ œ์ด์…˜์ด๋‹ค! ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ์ปดํŒŒ์ผ๋Ÿฌ ์ธก์—์„œ ์ด ํ•จ์ˆ˜๋ฅผ importํ•ด์„œ ์“ฐ๋ ค ํ•˜๋ฉด ํ„ฐ์ง„๋‹ค. (ํด๋ผ์ด์–ธํŠธ๋Š” ๊ธฐ์กด ๋ฐฉ์‹์˜ useMemo๋‚˜ useQuery ๋“ฑ์„ ์จ์•ผ ํ•จ)

โŒ (fetch) ์ค‘๋ณต ์ œ๊ฑฐ๊ฐ€ ์•ˆ ๋˜๊ณ  API๊ฐ€ 2๋ฒˆ ์ฐ”๋ ค์š”!

์›์ธ: Request Memoization์˜ ์ž‘๋™ ์กฐ๊ฑด์€ ์ฒ ์ €ํ•˜๊ฒŒ "๋ฉ”์†Œ๋“œ(GET ๋“ฑ)์™€ URL(๊ฒฝ๋กœ+์ฟผ๋ฆฌ์ŠคํŠธ๋ง)๊ณผ ์˜ต์…˜(headers ๋“ฑ)์ด 100% ์™„๋ฒฝํžˆ ์ผ์น˜ํ•  ๋•Œ" ๋งŒ ๋ฐœ๋™ํ•จ. ๋งŒ์•ฝ ํ•œ์ชฝ์—์„  Authorization ํ—ค๋”๋ฅผ ๋„ฃ๊ณ  ํ•œ์ชฝ์—์„  ๋นผ๋จน์—ˆ๋‹ค๋ฉด ๋ธŒ๋ผ์šฐ์ €(Next.js)๋Š” ๋‹ค๋ฅธ ์š”์ฒญ์œผ๋กœ ๊ฐ„์ฃผํ•˜๊ณ  ๋‘ ๋ฒˆ ์œ๋‹ค.
ํ•ด๊ฒฐ์ฑ…: ์•„์˜ˆ getUserProfileFetch() ๊ฐ™์ด fetch ์˜ต์…˜ ๋ฌด์žฅ์„ ๋๋งˆ์นœ ์œ ํ‹ธ ํ•จ์ˆ˜ ํ•˜๋‚˜๋ฅผ ๋งŒ๋“ค์–ด์„œ ๊ณณ๊ณณ์—์„œ ๋˜‘๊ฐ™์ด ๋ถˆ๋Ÿฌ๋‹ค ์“ฐ๋Š” ํŒจํ„ด์„ ์‚ฌ์šฉํ•ด๋ผ.


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

์ƒํ™ฉ (์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ ํ™˜๊ฒฝ)๊ณต๋ฃก ๋ ˆ๊ฑฐ์‹œ (๊ณผ๊ฑฐ) โŒํŠธ๋ Œ๋””ํ•œ ๋ฐฉ์‹ (ํ˜„์žฌ) โœ…
์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ฐ™์€ ๋ฐ์ดํ„ฐ ํ•„์š”ํ•  ๋•Œ์ตœ์ƒ๋‹จ์—์„œ ํ•œ ๋ฒˆ ํ˜ธ์ถœํ•ด Props Drilling์œผ๋กœ ๋‚ด๋ ค๊ฐ€๋ฉฐ ๊ณต์œ ๊ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ฐ์ž API๋ฅผ ํ˜ธ์ถœํ•จ. (Next.js๊ฐ€ ์•Œ์•„์„œ 1๊ฐœ๋กœ ํ•ฉ์ณ์คŒ)
ํด๋ผ์ด์–ธํŠธ ๋‹จ ์ „์—ญ ์ƒํƒœ (Zustand)์„œ๋ฒ„ ๋ฐ์ดํ„ฐ(์œ ์ €, ๊ฒŒ์‹œ๋ฌผ ๋“ฑ) ๋ณต์‚ฌํ•ด ๋‹ด๋Š” ๊ฑฐ๋Œ€ํ•œ ์“ฐ๋ ˆ๊ธฐํ†ต ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ์‹น ๊ฑท์–ด๋‚ด๊ณ , ๋ณต์žกํ•œ UI ๋ชจ๋‹ฌ ํ† ๊ธ€ ๊ฐ™์€ "์ˆœ์ˆ˜ ๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ(State)" ์ „์šฉ์œผ๋กœ๋งŒ ๋‚จ๊น€
ORM์ด๋‚˜ DB ํด๋ผ์ด์–ธํŠธ ์ฟผ๋ฆฌ ์ค‘๋ณต ๋ฐฉ์–ด๋ถˆ๊ฐ€๋Šฅ! ๋ฌด์กฐ๊ฑด ๋ถ€๋ชจ์—์„œ ์ฟผ๋ฆฌํ•ด์„œ ์ž์‹์—๊ฒŒ ๋ฟŒ๋ฆผํ•จ์ˆ˜ ์„ ์–ธ์„ React.cache(async () => DB์กฐํšŒ) ๋กœ ๊ฐ์‹ธ์„œ ์ˆ˜๋™์œผ๋กœ Request Memoization ํ˜œํƒ ์ ์šฉ

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ ์„ธ์ƒ์—์„œ๋Š” ๋ˆˆ์น˜ ๋ณด์ง€ ๋ง๊ณ  ํ•„์š”ํ•œ ๊ณณ์—์„œ ํ•„์š”ํ•œ ๋งŒํผ ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ์š”์ฒญํ•ด(Fetch)๋ผ. ๋˜‘๋˜‘ํ•œ ์ˆ˜๋ฌธ์žฅ(Next.js Memoization)์ด ๋‹ค ์•Œ์•„์„œ ์ค‘๋ณต ์š”๊ธˆ์„ ๋ฉด์ œํ•ด ์ค„ ๊ฑฐ๋‹ˆ๊นŒ. ๋‹จ, ์†๋‹˜์ด ์ง‘์— ๊ฐ€๋Š” ์ˆœ๊ฐ„ ๋ฉค๋ฒ„์‹ญ ๋ฌดํšจํ™”!


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

์˜ค๋Š˜์€ ์ •๋ง ํฐ ์ง์„ ํ•˜๋‚˜ ๋‚ด๋ ค๋†“์€ ๊ธฐ๋ถ„์ด์•ผ. ์˜ˆ์ „์—” ์ปดํฌ๋„ŒํŠธ๋งˆ๋‹ค ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ€๋ฅด๋ฉด ์„œ๋ฒ„๊ฐ€ ํ„ฐ์งˆ๊นŒ ๋ด ๋ฌด์„œ์›Œ์„œ Props Drilling ํ•˜๋А๋ผ ๋•€ ๋ป˜๋ป˜ ํ˜๋ ธ๊ฑฐ๋“ . ๊ทธ๋Ÿฐ๋ฐ ์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์ด ๋ง์”€ํ•˜์‹  '๋„๋งˆ ์œ„์˜ ๊ณ ๊ธฐ(Memoization)' ๋•๋ถ„์— ์ด์ œ๋Š” ๋‹น๋‹นํ•˜๊ฒŒ ๊ฐ์ž ํ•„์š”ํ•œ ๊ณณ์—์„œ fetch ๋ฅผ ๋‚ ๋ฆด ์ˆ˜ ์žˆ๊ฒŒ ๋์–ด!

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋ผ๋ฉด Props๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์˜ฎ๊ธฐ์ง€ ๋ง๊ณ , ํ•„์š”ํ•œ ๊ณณ์—์„œ ์ง์ ‘ fetchํ•˜์ž! ๋‚˜๋จธ์ง€๋Š” ๋ฆฌ์•กํŠธ ์ฃผ๋ฐฉ์žฅ์ด ์•Œ์•„์„œ ๋ณ‘ํ•ฉํ•ด์ค€๋‹ค."

React.cache() ๋ผ๋Š” ๋“ ๋“ ํ•œ ๋ฐฉ์–ด๋ง‰๋„ ๋ฐฐ์› ์œผ๋‹ˆ, ์ด์ œ ์šฐ๋ฆฌ ์„œ๋น„์Šค์˜ DB ๋ถ€ํ•˜๋„ ๊ฑฑ์ • ์—†์–ด. ์˜ค๋Š˜์€ ๋จธ๋ฆฌ๋ฅผ ๋งŽ์ด ์ผ๋”๋‹ˆ ์ข€ ๋ฉํ•˜๋„ค. ์ง‘์— ๊ฐ€์„œ ์‹œ์›ํ•˜๊ฒŒ ์ƒค์›Œํ•˜๊ณ  ํ‘น ์ž์•ผ์ง€. ๋‚ด์ผ์€ ์˜ค๋Š˜๋ณด๋‹ค ๋” ๋น ๋ฅด๊ณ  ํšจ์œจ์ ์ธ ๋ฐ์ดํ„ฐ ํŽ˜์นญ ์ฝ”๋“œ๋ฅผ ์งœ๋ณผ ๊ฑฐ์•ผ! ๐Ÿฃ


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