(๋ฒˆ์—ญ) ํ•œ ๋ฒˆ์˜ ์™•๋ณต ์š”์ฒญ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋Š” ๋‚ด๋น„๊ฒŒ์ด์…˜

์›๋ฌธ : One Roundtrip Per Navigation

๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋ ค๋ฉด ๋ช‡ ๋ฒˆ์˜ ์š”์ฒญ์ด ํ•„์š”ํ• ๊นŒ์š”?

๊ฐ€์žฅ ๋‹จ์ˆœํ•œ ๊ฒฝ์šฐ๋Š” ๋‹จ ํ•œ ๋ฒˆ์˜ ์š”์ฒญ๋งŒ์œผ๋กœ ํ•ด๊ฒฐ๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋งํฌ๋ฅผ ํด๋ฆญํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๋Š” ์ƒˆ URL์— ๋Œ€ํ•œ HTML ์ฝ˜ํ…์ธ ๋ฅผ ์š”์ฒญํ•˜๊ณ , ๊ทธ ์ฝ˜ํ…์ธ ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

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

๊ทธ๋ ‡๋‹ค๋ฉด, ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•  ๋•Œ๋Š” ์–ด๋–จ๊นŒ์š”?

๋‹ค์Œ ํŽ˜์ด์ง€์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ๋ช‡ ๋ฒˆ์˜ API ์š”์ฒญ์ด ํ•„์š”ํ• ๊นŒ์š”?


HTML

์›น ๊ฐœ๋ฐœ์ด ํด๋ผ์ด์–ธํŠธ๋กœ ์˜ฎ๊ฒจ์˜ค๊ธฐ ์ „์—๋Š”, ์ด๋Ÿฐ ์งˆ๋ฌธ ์ž์ฒด๊ฐ€ ์˜๋ฏธ๊ฐ€ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. โ€œAPI๋ฅผ ํ˜ธ์ถœํ•œ๋‹คโ€๋Š” ๊ฐœ๋…์ด ์—†์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด์ฃ . ์„œ๋ฒ„๋Š” ๊ทธ๋ƒฅ HTML์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์„œ๋ฒ„์ผ ๋ฟ, API ์„œ๋ฒ„๋กœ ์ƒ๊ฐ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

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

<article>
  <h1>ํ•œ ๋ฒˆ์˜ ์™•๋ณต ์š”์ฒญ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋Š” ๋‚ด๋น„๊ฒŒ์ด์…˜</h1>
  <p>๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋ ค๋ฉด ๋ช‡ ๋ฒˆ์˜ ์š”์ฒญ์ด ํ•„์š”ํ• ๊นŒ์š”?</p>
  <ul class="comments">
    <li>HTML์˜ ์žฌ์ฐฝ์กฐ</li>
    <li>PHP์˜ ์žฌ์ฐฝ์กฐ</li>
    <li>GraphQL์˜ ์žฌ์ฐฝ์กฐ</li>
    <li>Remix์˜ ์žฌ์ฐฝ์กฐ</li>
    <li>Astro์˜ ์žฌ์ฐฝ์กฐ</li>
  </ul>
</article>

(๋ฌผ๋ก  ๊ธฐ์ˆ ์ ์œผ๋กœ๋Š” ์ด๋ฏธ์ง€, ์Šคํฌ๋ฆฝํŠธ, ์Šคํƒ€์ผ๊ณผ ๊ฐ™์€ ์ •์ ์ด๊ณ  ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ ์บ์‹œ ๊ฐ€๋Šฅํ•œ ์š”์†Œ๋“ค์ด โ€œ์™ธ๋ถ€ํ™”โ€๋˜์ง€๋งŒ, ํ•„์š”์— ๋”ฐ๋ผ ์–ธ์ œ๋“ ์ง€ ์ธ๋ผ์ธ ์ฒ˜๋ฆฌํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.)


โ€œRESTโ€

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง์ด ์ ์  ํด๋ผ์ด์–ธํŠธ๋กœ ์ด๋™ํ•˜๋ฉด์„œ ์ƒํ™ฉ์ด ๋‹ฌ๋ผ์กŒ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ๊ฐ€์ ธ์˜ค๊ณ ์ž ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ํ‘œ์‹œํ•˜๊ณ ์ž ํ•˜๋Š” UI์— ๋”ฐ๋ผ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค. ๊ฒŒ์‹œ๊ธ€์„ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ๋‹ค๋ฉด ๊ฒŒ์‹œ๊ธ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•˜๊ณ , ๋Œ“๊ธ€์„ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ๋‹ค๋ฉด ๋Œ“๊ธ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿผ ์–ผ๋งˆ๋‚˜ ์—ฌ๋Ÿฌ๋ฒˆ ๊ฐ€์ ธ์™€์•ผ ํ• ๊นŒ์š”?

JSON API์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜๋Š” REST๋ผ๋Š” ๊ธฐ์ˆ ์„ ํ†ตํ•ด ๊ฐœ๋…์ ์ธ โ€œ๋ฆฌ์†Œ์Šคโ€๋งˆ๋‹ค ํ•˜๋‚˜์˜ ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋…ธ์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค. โ€œ๋ฆฌ์†Œ์Šคโ€๊ฐ€ ์ •ํ™•ํžˆ ๋ฌด์—‡์ธ์ง€๋Š” ์•„๋ฌด๋„ ๋ชจ๋ฅด์ง€๋งŒ, ๋ณดํ†ต์€ ๋ฐฑ์—”๋“œ ํŒ€์ด ์ด ๊ฐœ๋…์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๊ฒŒ์‹œ๊ธ€ โ€œ๋ฆฌ์†Œ์Šคโ€์™€ ๋Œ“๊ธ€ โ€œ๋ฆฌ์†Œ์Šคโ€๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์ด๋กœ ์ธํ•ด ๊ฒŒ์‹œ๊ธ€ ํŽ˜์ด์ง€(๊ฒŒ์‹œ๊ธ€๊ณผ ๋Œ“๊ธ€์„ ํฌํ•จ)์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‘ ๋ฒˆ ๊ฐ€์ ธ์™€์„œ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ์ด ๋‘ ๋ฒˆ์˜ ๊ฐ€์ ธ์˜ค๊ธฐ๋Š” ์–ด๋””์„œ ๋ฐœ์ƒํ• ๊นŒ์š”?

์„œ๋ฒ„ ์ค‘์‹ฌ์˜ HTML ์•ฑ(์›น์‚ฌ์ดํŠธ)์—์„œ๋Š” ํ•˜๋‚˜์˜ ์š”์ฒญ ์ค‘์— ๋‘ ๊ฐœ์˜ REST API๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , ์—ฌ์ „ํžˆ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜์˜ ์‘๋‹ต์œผ๋กœ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” REST API ์š”์ฒญ์ด ์„œ๋ฒ„์—์„œ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. REST API๋Š” ์ฃผ๋กœ ๋ฐ์ดํ„ฐ ๊ณ„์ธต์˜ ๋ช…์‹œ์ ์ธ ๊ฒฝ๊ณ„๋ฅผ ์œ„ํ•œ ์ˆ˜๋‹จ์œผ๋กœ ์‚ฌ์šฉ๋˜์—ˆ์ง€๋งŒ, ๊ผญ ํ•„์š”ํ•˜์ง€๋Š” ์•Š์•˜์Šต๋‹ˆ๋‹ค(๋งŽ์€ ๊ฒฝ์šฐ์—๋Š” Rails๋‚˜ Django์ฒ˜๋Ÿผ ์ธํ”„๋กœ์„ธ์Šค ๋ฐ์ดํ„ฐ ๊ณ„์ธต์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ ๋งŒ์กฑํ–ˆ์Šต๋‹ˆ๋‹ค). REST ์—ฌ๋ถ€์™€ ์ƒ๊ด€์—†์ด, ๋ฐ์ดํ„ฐ(HTML)๋Š” ํด๋ผ์ด์–ธํŠธ(๋ธŒ๋ผ์šฐ์ €)์— ์˜จ์ „ํ•œ ์ƒํƒœ๋กœ ๋„์ฐฉํ•ฉ๋‹ˆ๋‹ค.

์ƒํ˜ธ์ž‘์šฉ์„ ์œ„ํ•ด UI ๋กœ์ง์ด ํด๋ผ์ด์–ธํŠธ๋กœ ์˜ฎ๊ฒจ๊ฐ€๊ธฐ ์‹œ์ž‘ํ•˜๋ฉด์„œ, ๊ธฐ์กด REST API๋ฅผ ๊ทธ๋Œ€๋กœ ๋‘๊ณ  ํด๋ผ์ด์–ธํŠธ์—์„œ ์ด๋ฅผ fetchํ•˜๋Š” ๊ฒƒ์ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋А๊ปด์กŒ์Šต๋‹ˆ๋‹ค. JSON API์˜ ์œ ์—ฐ์„ฑ์€ ๋ฐ”๋กœ ์ด๋Ÿฐ ์ƒํ™ฉ์— ์ข‹๋‹ค๊ณ  ์ƒ๊ฐ๋˜์—ˆ์ฃ . ๋ชจ๋“  ๊ฒƒ์ด JSON API๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

const [post, comments] = await Promise.all([
  fetch(`/api/posts/${postId}`).then(res => res.json()),
  fetch(`/api/posts/${postId}/comments`).then(res => res.json()),
]);

๊ทธ๋Ÿฌ๋‚˜ ๊ทธ ๊ฒฐ๊ณผ, ๋„คํŠธ์›Œํฌ ํƒญ์—๋Š” ์ด์ œ ๋‘ ๊ฐœ์˜ ๊ฐ€์ ธ์˜ค๊ธฐ๊ฐ€ ๋ณด์ž…๋‹ˆ๋‹ค. ํ•˜๋‚˜๋Š” ๊ฒŒ์‹œ๊ธ€์— ๋Œ€ํ•œ ๊ฐ€์ ธ์˜ค๊ธฐ์ด๊ณ , ๋‹ค๋ฅธ ํ•˜๋‚˜๋Š” ๊ฒŒ์‹œ๊ธ€์˜ ๋Œ“๊ธ€์— ๋Œ€ํ•œ ๊ฐ€์ ธ์˜ค๊ธฐ์ž…๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ํŽ˜์ด์ง€, ํ•˜๋‚˜์˜ ๋งํฌ ํด๋ฆญ์ด ์ข…์ข… ๋‘ ๊ฐœ ์ด์ƒ์˜ REST โ€œ๋ฆฌ์†Œ์Šคโ€๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ํ•„์š”๋กœ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€์žฅ ์ข‹์€ ๊ฒฝ์šฐ์—๋Š” ๋‘์„ธ ๊ฐœ์˜ ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๋๋‚˜์ง€๋งŒ, ์ตœ์•…์˜ ๊ฒฝ์šฐ์—๋Š” N๊ฐœ์˜ ํ•ญ๋ชฉ๋งˆ๋‹ค N๊ฐœ์˜ ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜, ํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„ ๊ฐ„์— ์—ฐ์†์ ์ธ ์›Œํ„ฐํด ์š”์ฒญ์„ ํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค(์ผ๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•œ ํ›„, ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ค์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญ).

๋น„ํšจ์œจ์ด ์„œ์„œํžˆ ๋ฐœ์ƒํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„์— ์žˆ์„ ๋•Œ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ REST ์š”์ฒญ์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์ €๋ ดํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฐฐํฌ๋œ ์ฝ”๋“œ๋ฅผ ํ†ต์ œํ•  ์ˆ˜ ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. REST ์—”๋“œํฌ์ธํŠธ๊ฐ€ ๋ฉ€๋ฆฌ ์žˆ์œผ๋ฉด ์„œ๋ฒ„๋ฅผ ๊ทธ ๊ฐ€๊นŒ์ด๋กœ ์˜ฎ๊ธฐ๊ฑฐ๋‚˜, ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์ธํ”„๋กœ์„ธ์Šค ์ฝ”๋“œ๋กœ ์ด์ „ํ•  ์ˆ˜๋„ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ณต์ œ๋‚˜ ์„œ๋ฒ„ ์ธก ์บ์‹ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ญ”๊ฐ€ ๋น„ํšจ์œจ์ ์ด๋”๋ผ๋„, ์„œ๋ฒ„ ์ชฝ์—๋Š” ์ด๋ฅผ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค์–‘ํ•œ ์ˆ˜๋‹จ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ฌด๊ฒƒ๋„ ์„œ๋ฒ„ ์ธก ๊ฐœ์„ ์„ ๋ง‰์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์„œ๋ฒ„๋ฅผ ๋ธ”๋ž™๋ฐ•์Šค๋กœ ๋ณธ๋‹ค๋ฉด, ์„œ๋ฒ„๊ฐ€ ์ œ๊ณตํ•˜๋Š” API๋ฅผ ๊ฐœ์„ ํ•  ์—ฌ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„๊ฐ€ ์š”์ฒญ์„ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์œผ๋ฉด, ํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„ ์›Œํ„ฐํด์„ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„๊ฐ€ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐฐ์น˜๋กœ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์œผ๋ฉด ๋ณ‘๋ ฌ ์š”์ฒญ ์ˆ˜๋ฅผ ์ค„์ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

์–ด๋А ์ˆœ๊ฐ„, ํ•œ๊ณ„์— ๋„๋‹ฌํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.


์ปดํฌ๋„ŒํŠธ

์œ„์—์„œ ์„ค๋ช…ํ•œ ๋ฌธ์ œ๋Š” ํšจ์œจ์„ฑ๊ณผ ์บก์Аํ™” ๊ฐ„์˜ ๊ธด์žฅ ์ƒํƒœ๊ฐ€ ์—†์—ˆ๋‹ค๋ฉด ๊ทธ๋ฆฌ ๋‚˜์˜์ง€ ์•Š์•˜์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๋กœ์„œ ์šฐ๋ฆฌ๋Š” ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ๋กœ์ง์„ ๊ทธ ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ฌ์šฉ๋˜๋Š” ์œ„์น˜ ๊ฐ€๊นŒ์ด์— ๋ฐฐ์น˜ํ•˜๊ณ  ์‹ถ์€ ์š•๊ตฌ๋ฅผ ๋А๋‚๋‹ˆ๋‹ค. ๋ˆ„๊ตฐ๊ฐ€๋Š” ์ด๊ฒƒ์ด โ€œ์ŠคํŒŒ๊ฒŒํ‹ฐ ์ฝ”๋“œโ€๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋งํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ๊ผญ ๊ทธ๋Ÿฐ ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค! ๊ทธ ์•„์ด๋””์–ด ์ž์ฒด๋Š” ํ•ฉ๋ฆฌ์ ์ž…๋‹ˆ๋‹ค. ๊ธฐ์–ตํ•˜์„ธ์š”. UI๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” ์šฐ๋ฆฌ๊ฐ€ ๋ฌด์—‡์„ ํ‘œ์‹œํ•˜๊ณ  ์‹ถ์€์ง€์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ ๋กœ์ง๊ณผ UI ๋กœ์ง์€ ๋ณธ์งˆ์ ์œผ๋กœ ๋ฐ€์ ‘ํ•˜๊ฒŒ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์œผ๋ฉฐ, ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๋‹ค๋ฅธ ํ•˜๋‚˜๋„ ๋ฐ˜๋“œ์‹œ ๊ทธ์— ๋งž์ถฐ ์กฐ์ •๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ โ€œ์ ๊ฒŒ ๊ฐ€์ ธ์™€์„œ(underfetching)โ€ ๊ธฐ๋Šฅ์„ ๊นจ๋œจ๋ฆฌ๊ฑฐ๋‚˜, โ€œ๋„ˆ๋ฌด ๋งŽ์ด ๊ฐ€์ ธ์™€(overfetching)โ€ ์„ฑ๋Šฅ์„ ์ €ํ•ดํ•˜๊ณ  ์‹ถ์ง€๋Š” ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด UI ๋กœ์ง๊ณผ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์„ ์–ด๋–ป๊ฒŒ ๋™๊ธฐํ™”ํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?

๊ฐ€์žฅ ์ง์ ‘์ ์ธ ๋ฐฉ๋ฒ•์€ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ๋กœ์ง์„ UI ์ปดํฌ๋„ŒํŠธ ์•ˆ์— ์ง์ ‘ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” โ€˜Backbone.View์—์„œ $.ajax๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹โ€™ ๋˜๋Š” โ€˜useEffect์—์„œ fetch๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹โ€™์œผ๋กœ, ํด๋ผ์ด์–ธํŠธ ์ธก UI๊ฐ€ ๋ถ€์ƒํ•˜๋ฉด์„œ ํญ๋ฐœ์ ์œผ๋กœ ์ธ๊ธฐ๋ฅผ ๋Œ์—ˆ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๋„ ์—ฌ์ „ํžˆ ๋งŽ์ด ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ ‘๊ทผ์˜ ์žฅ์ ์€ ์ฝ”๋“œ์˜ ๊ทผ์ ‘์„ฑ(colocation)์— ์žˆ์Šต๋‹ˆ๋‹ค. ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋”ฉํ• ์ง€์— ๋Œ€ํ•œ ์ฝ”๋“œ๊ฐ€, ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ์†Œ๋น„ํ•˜๋Š” ์ฝ”๋“œ ๋ฐ”๋กœ ์˜†์— ์œ„์น˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์„œ๋กœ ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑํ•œ ๋’ค, ์ด๋“ค์„ ํ•˜๋‚˜๋กœ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function PostContent({ postId }) {
  const [post, setPost] = useState();
  useEffect(() => {
    fetch(`/api/posts/${postId}`)
      .then(res => res.json())
      .then(setPost);
  }, []);
  if (!post) {
    return null;
  }
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <Comments postId={postId} />
    </article>
  );
}

function Comments({ postId }) {
  const [comments, setComments] = useState([]);
  useEffect(() => {
    fetch(`/api/posts/${postId}/comments`)
      .then(res => res.json())
      .then(setComments);
  }, []);
  return (
    <ul className="comments">
      {comments.map(c => (
        <li key={c.id}>{c.text}</li>
      ))}
    </ul>
  );
}

ํ•˜์ง€๋งŒ ์ด ์ ‘๊ทผ์€ ์•ž์„œ ์„ค๋ช…ํ•œ ๋ฌธ์ œ๋ฅผ ํ›จ์”ฌ ๋” ์‹ฌ๊ฐํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋‹จ์ผ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์š”์ฒญ์ด ํ•„์š”ํ•  ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ์ด ์š”์ฒญ๋“ค์ด ์ฝ”๋“œ๋ฒ ์ด์Šค ์ „๋ฐ˜์— ๋ถ„์‚ฐ๋˜์–ด ์žˆ๋‹ค๋Š” ์ ์—์„œ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. ๊ทธ ๋น„ํšจ์œจ์„ ์–ด๋–ป๊ฒŒ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?

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

๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์ฝ”๋“œ์— ๊ตฌ์กฐ๋ฅผ ์กฐ๊ธˆ ๋” ์ถ”๊ฐ€ํ•˜๋ฉด ๋„์›€์ด ๋  ์ˆ˜ ์žˆ๋Š”์ง€ ์‚ดํŽด๋ด…์‹œ๋‹ค.


์ฟผ๋ฆฌ

React Query์˜ useQuery์™€ ๊ฐ™์ด ๋ฐ์ดํ„ฐ ์š”์ฒญ์— ๊ตฌ์กฐ๋ฅผ ๋ถ€์—ฌํ•˜๋ ค๋Š” ์†”๋ฃจ์…˜์€ ์œ„ ๋ฌธ์ œ์— ๋Œ€ํ•œ ๊ถ๊ทน์ ์ธ ํ•ด๊ฒฐ์ฑ…์ด ์•„๋‹™๋‹ˆ๋‹ค. ์ด๋“ค์€ useEffect์—์„œ fetch๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค๋Š” ํ›จ์”ฌ ๋” ์›์น™์ ์ธ ๋ฐฉ์‹์ด๋ฉฐ, ์บ์‹ฑ์ด ๋„์›€์ด ๋˜๊ธด ํ•˜์ง€๋งŒ, ์—ฌ์ „ํžˆ โ€œN๊ฐœ์˜ ํ•ญ๋ชฉ์— ๋Œ€ํ•ด N๊ฐœ์˜ ์ฟผ๋ฆฌโ€์™€ โ€œํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„ ๊ฐ„์˜ ์ฟผ๋ฆฌ ์›Œํ„ฐํดโ€ ๋ฌธ์ œ์—์„œ ์ž์œ ๋กญ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

function usePostQuery(postId) {
  return useQuery(['post', postId], () =>
    fetch(`/api/posts/${postId}`).then(res => res.json())
  );
}

function usePostCommentsQuery(postId) {
  return useQuery(['post-comments', postId], () =>
    fetch(`/api/posts/${postId}/comments`).then(res => res.json())
  );
}

function PostContent({ postId }) {
  const { data: post } = usePostQuery(postId);
  if (!post) {
    return null;
  }
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <Comments postId={postId} />
    </article>
  );
}

function Comments({ postId }) {
  const { data: comments } = usePostCommentsQuery(postId);
  return (
    <ul className="comments">
      {comments.map(c => (
        <li key={c.id}>{c.text}</li>
      ))}
    </ul>
  );
}

์‚ฌ์‹ค, ํด๋ผ์ด์–ธํŠธ ์ธก ์บ์‹ฑ์ด ๋งŒ๋Šฅ ํ•ด๊ฒฐ์ฑ…์€ ์•„๋‹™๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์•ฑ์—์„œ โ€œ๋’ค๋กœ ๊ฐ€๊ธฐโ€ ๋ฒ„ํŠผ์ด ์ฆ‰์‹œ ๋™์ž‘ํ•˜๋„๋ก ํ•˜๋ ค๋ฉด ์บ์‹ฑ์ด ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•˜๊ณ , ํƒญ ์ „ํ™˜์ฒ˜๋Ÿผ ํŠน์ • ๋‚ด๋น„๊ฒŒ์ด์…˜์—์„œ ์บ์‹œ ์žฌ์‚ฌ์šฉ์€ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋งŽ์€ ๋‚ด๋น„๊ฒŒ์ด์…˜, ํŠนํžˆ ๋งํฌ ํด๋ฆญ์—์„œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์‹ ์„ ํ•œ ์ฝ˜ํ…์ธ ๋ฅผ ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ”๋กœ ์ด๊ฒƒ์ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ HTML ์•ฑ์—์„œ ํŽ˜์ด์ง€๋ฅผ ๋กœ๋”ฉํ•  ๋•Œ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ด์œ ์ž…๋‹ˆ๋‹ค! ์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€ ์ „์ฒด๊ฐ€ ๋Œ€์ฒด๋˜๊ธฐ๋ฅผ ์›ํ•˜์ง€๋Š” ์•Š๋”๋ผ๋„(ํŠนํžˆ ์•ฑ์ด ๋‚ด๋น„๊ฒŒ์ด์…˜ ์…ธ(shell)์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ), ์ฝ˜ํ…์ธ  ์˜์—ญ์€ ๋งํฌ ํด๋ฆญ ํ›„์— ์‹ ์„ ํ•˜๊ธธ ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค. (๋ฌผ๋ก , ๋งˆ์šฐ์Šค ์˜ค๋ฒ„ ์‹œ ํ”„๋ฆฌํŽ˜์นญ์„ ํ†ตํ•ด ์‹ ์„ ํ•˜๋ฉด์„œ๋„ ์ฆ‰๊ฐ์ ์ธ ๋‚ด๋น„๊ฒŒ์ด์…˜์„ ์ œ๊ณตํ•˜๋Š” ๊ฑด ํ›จ์”ฌ ๋” ์ข‹์Šต๋‹ˆ๋‹ค.)

์ง๊ด€๊ณผ ๋‹ฌ๋ฆฌ, ๋” ๋น ๋ฅด๋‹ค๊ณ  ํ•ด์„œ ํ•ญ์ƒ ๋” ๋‚˜์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ์บ์‹œ๋œ ์˜ค๋ž˜๋œ ์ฝ˜ํ…์ธ ๊ฐ€ ์ž ๊น ๋‚˜ํƒ€๋‚ฌ๋‹ค๊ฐ€ ๋ฐ”๋กœ ๊ต์ฒด๋˜๋Š” ๋ฐฉ์‹(์˜ˆ: stale-while-revalidate)์€ ์˜คํžˆ๋ ค ์‚ฌ์šฉ์ž์˜ ์˜๋„๋ฅผ ์ €๋ฒ„๋ฆฌ๋Š”์ผ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ๋งํฌ๋ฅผ ํด๋ฆญํ•  ๋•Œ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค. โ€œํ˜น์‹œ ๋ชฐ๋ผ์„œโ€ Ctrl+R์„ ๋ˆ„๋ฅด๊ฒŒ ๋งŒ๋“ค๊ณ  ์‹ถ์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.

ํด๋ผ์ด์–ธํŠธ ์ธก ์บ์‹ฑ์€ ์ฝ˜ํ…์ธ ๊ฐ€ ์•„์ง ๋ฐ”๋€Œ์ง€ ์•Š์•˜๊ฑฐ๋‚˜, ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฐ˜์˜ํ•  ํ•„์š”๊ฐ€ ์—†์„ ๋•Œ๋Š” ์œ ์šฉํ•˜์ง€๋งŒ, ๋งŒ๋ณ‘ํ†ต์น˜์•ฝ์ด ์•„๋‹ˆ๋ฉฐ ๋‹ค๋ฅธ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์–‘ํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์ง€๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ตœ์‹  ์ƒํƒœ๋กœ ์œ ์ง€ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ์š”์ฒญ ํšŸ์ˆ˜๋ฅผ ์ค„์ด์ง€๋Š” ๋ชปํ•˜๋ฉฐ ํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„ ์›Œํ„ฐํด์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด์ œ ์šฐ๋ฆฌ๋Š” UI์™€ ๋ฐ์ดํ„ฐ ์š”๊ตฌ์‚ฌํ•ญ์„ ๊ทผ์ ‘ํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ณ  ์‹ถ์ง€๋งŒ, ๋™์‹œ์— ์›Œํ„ฐํด ์š”์ฒญ์„ ํ”ผํ•˜๊ณ  ๊ณผ๋„ํ•œ ๋ณ‘๋ ฌ ์š”์ฒญ๋„ ๋ง‰๊ณ  ์‹ถ๋‹ค๋Š” ๊ธด์žฅ๊ฐ์ด ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์ฟผ๋ฆฌ ์บ์‹œ๋งŒ์œผ๋กœ๋Š” ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด, ์šฐ๋ฆฌ๋Š” ๋ฌด์—‡์„ ํ•ด์•ผ ํ• ๊นŒ์š”?


ํด๋ผ์ด์–ธํŠธ ๋กœ๋”(loader)

์šฐ๋ฆฌ๊ฐ€ ํ•  ์ˆ˜ ์žˆ๋Š” ์ผ ์ค‘ ํ•˜๋‚˜๋Š” ์ฝ”๋“œ ๊ทผ์ ‘์„ฑ์„ ํฌ๊ธฐํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฐ ๋ผ์šฐํŠธ๋งˆ๋‹ค ํ•ด๋‹น ๋ผ์šฐํŠธ์— ํ•„์š”ํ•œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค. ์ด ํ•จ์ˆ˜๋ฅผ ๋กœ๋”๋ผ๊ณ  ๋ถ€๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค.

async function clientLoader({ params }) {
  const { postId } = params;
  const [post, comments] = await Promise.all([
    fetch(`/api/posts/${postId}`).then(res => res.json()),
    fetch(`/api/posts/${postId}/comments`).then(res => res.json()),
  ]);
  return { post, comments };
}

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

์ด ์ ‘๊ทผ ๋ฐฉ์‹์˜ ๋‹จ์ ์€, ๋ฐ์ดํ„ฐ ์š”๊ตฌ์‚ฌํ•ญ์ด ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ํ•„์š”๋กœ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค๊ณผ ๋” ์ด์ƒ ๋‚˜๋ž€ํžˆ ์žˆ์ง€ ์•Š๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ๊ฐ ๋ผ์šฐํŠธ์˜ โ€œ์ƒ๋‹จโ€์— ์žˆ๋Š” ์ฝ”๋“œ๊ฐ€ ๊ทธ ์•„๋ž˜์— ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๋“ค์ด ์žˆ๊ณ , ๊ทธ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ํ•„์š”๋กœ ํ•˜๋Š”์ง€๋ฅผ โ€œ์•Œ๊ณ  ์žˆ์–ด์•ผโ€ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ ์—์„œ ๋ณด๋ฉด, ์ฟผ๋ฆฌ๋‚˜ ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ์‹์— ๋น„ํ•ด ํ•œ ๋‹จ๊ณ„ ํ›„ํ‡ดํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๋А๊ปด์ง‘๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด ์ ‘๊ทผ์˜ ์žฅ์ ์€ ํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„ ์›Œํ„ฐํด์„ ํ›จ์”ฌ ๋” ์‰ฝ๊ฒŒ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ฌผ๋ก  ์—ฌ์ „ํžˆ clientLoader ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๋ฏ€๋กœ ์›Œํ„ฐํด์ด ์ƒ๊ธธ ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์ด์ œ ๊ทธ ๊ตฌ์กฐ๊ฐ€ ๋ˆˆ์— ๋ณด์ž…๋‹ˆ๋‹ค. ์ฆ‰, ์ปดํฌ๋„ŒํŠธ๋‚˜ ์ฟผ๋ฆฌ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ์ฒ˜๋Ÿผ ๊ธฐ๋ณธ์ ์œผ๋กœ ์›Œํ„ฐํด์ด ๋ฐœ์ƒํ•˜๋Š” ๋ฐฉ์‹์€ ์•„๋‹™๋‹ˆ๋‹ค.


์„œ๋ฒ„ ๋กœ๋”

๋กœ๋”๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๋˜ ๋‹ค๋ฅธ ์žฅ์ ์€, ๊ฐ ๋ผ์šฐํŠธ๊ฐ€ ๋…๋ฆฝ์ ์ธ ๋กœ๋”๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด ์ด ๋กœ์ง์˜ ์ผ๋ถ€๋ฅผ ์„œ๋ฒ„๋กœ ์˜ฎ๊ธฐ๊ธฐ๊ฐ€ ํ›จ์”ฌ ์‰ฌ์›Œ์ง„๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ๋กœ๋”๋Š” ์ปดํฌ๋„ŒํŠธ์™€ ๋…๋ฆฝ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋ฉฐ(์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง๋˜๊ธฐ ์ „์— ์‹คํ–‰๋จ), ๋”ฐ๋ผ์„œ HTML ๋˜๋Š” API ์„œ๋ฒ„์˜ ์ผ๋ถ€๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜๋„ ์žˆ๊ณ , ์•„์˜ˆ ๋ณ„๋„์˜ โ€œBFF(Backend for Frontend)โ€ ์„œ๋ฒ„๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

// ์ด ์ฝ”๋“œ๋Š” ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
async function loader({ params }) {
  const { postId } = params;
  const [post, comments] = await Promise.all([
    fetch(`/api/posts/${postId}`).then(res => res.json()),
    fetch(`/api/posts/${postId}/comments`).then(res => res.json()),
  ]);
  return { post, comments };
}

์ด๊ฒƒ์€ ๋ฆฌ์•กํŠธ ๋ผ์šฐํ„ฐ์˜ loader ํ•จ์ˆ˜๋‚˜, ์ด์ „ Next.js์˜ getServerSideProps()๊ฐ€ ๋”ฐ๋ž๋˜ ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค. ๋ณดํ†ต ๋นŒ๋“œ ์‹œ์ ์˜ ์ฝ”๋“œ ๋ณ€ํ™˜์„ ํ†ตํ•ด ์ด ๋กœ๋” ์ฝ”๋“œ๋ฅผ ํด๋ผ์ด์–ธํŠธ์šฉ ์ฝ”๋“œ์™€ โ€œ๋ถ„๋ฆฌโ€ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด, ์™œ ๋กœ๋”๋ฅผ ์„œ๋ฒ„๋กœ ์˜ฎ๊ธฐ๋Š” ๊ฑธ๊นŒ์š”?

์„œ๋ฒ„๋ฅผ ๋‹จ์ˆœํ•œ ๋ธ”๋ž™๋ฐ•์Šค๋กœ ๋ณด์ง€ ์•Š๋Š”๋‹ค๋ฉด, ์„œ๋ฒ„๋Š” ๋ฐ์ดํ„ฐ ์š”์ฒญ ์ฝ”๋“œ๋ฅผ ๋ฐฐ์น˜ํ•˜๊ธฐ์— ๊ฐ€์žฅ ์ž์—ฐ์Šค๋Ÿฌ์šด ์žฅ์†Œ์ž…๋‹ˆ๋‹ค. ์„œ๋ฒ„๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ˜๋‹จ๋“ค์„ ๋งŽ์ด ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ง€์—ฐ ์‹œ๊ฐ„์„ ์ค„์ด๊ธฐ ์œ„ํ•ด BFF ์„œ๋ฒ„๋ฅผ ๋ฐ์ดํ„ฐ ์†Œ์Šค ๊ฐ€๊นŒ์ด ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๋‚ด์žฌ๋œ ์›Œํ„ฐํด ์š”์ฒญ๋„ ์ €๋ ดํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์†Œ์Šค๊ฐ€ ๋А๋ฆด ๊ฒฝ์šฐ์—๋„, ์„œ๋ฒ„์—์„œ๋Š” ํฌ๋กœ์Šค ์š”์ฒญ ์บ์‹œ ๊ฐ™์€ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์ „์ฒด๋ฅผ ํฌ๊ธฐํ•˜๊ณ  Rails์—์„œ ์ฒ˜๋Ÿผ ๋ฐ์ดํ„ฐ ๊ณ„์ธต์„ ์ธํ”„๋กœ์„ธ์Šค๋กœ ์˜ฎ๊ธธ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

import { loadPost, loadComments } from 'my-data-layer';
async function loader({ params }) {
  const { postId } = params;
  const [post, comments] = await Promise.all([
    loadPost(postId),    loadComments(postId),  ]);
  return { post, comments };
}

์ธํ”„๋กœ์„ธ์Šค ๋ฐ์ดํ„ฐ ๊ณ„์ธต์€ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ ์ตœ๊ณ ์˜ ๊ธฐํšŒ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋” ๋‚ฎ์€ ์ˆ˜์ค€์œผ๋กœ ๋‚ด๋ ค๊ฐ€์„œ ํŠน์ • ํ™”๋ฉด์„ ์œ„ํ•œ ์ €์žฅ ํ”„๋กœ์‹œ์ €(stored procedure)๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์š”์ฒญ ๋‹น ๋ฉ”๋ชจ๋ฆฌ ๋‚ด ์บ์‹ฑ๊ณผ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•ด DB ํ˜ธ์ถœ ํšŸ์ˆ˜๋ฅผ ๋” ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ค๋ฒ„ํŽ˜์นญ์ด๋‚˜ ์–ธ๋”ํŽ˜์นญ์— ๋Œ€ํ•ด ๊ฑฑ์ •ํ•  ํ•„์š”๋„ ์—†์Šต๋‹ˆ๋‹ค. ๊ฐ ๋กœ๋”๋Š” ํ•ด๋‹น ํ™”๋ฉด์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์ •ํ™•ํ•˜๊ฒŒ ์ „๋‹ฌํ•˜๋ฉด ๋˜๋‹ˆ๊นŒ์š”. ๋” ์ด์ƒ โ€œRESTโ€ โ€œ๋ฆฌ์†Œ์Šคโ€๋ฅผ โ€œํ™•์žฅโ€ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์„ค๋ น REST API ํ˜ธ์ถœ์„ ์‚ฌ์šฉํ•˜๋”๋ผ๋„, ์šฐ๋ฆฌ๋Š” ์ „ํ†ต์ ์ธ โ€œHTML ์•ฑโ€์˜ ์œ ์šฉํ•œ ํŠน์„ฑ, ์ฆ‰, Rails๋‚˜ Django๋กœ ๊ตฌ์„ฑ๋œ ์•„ํ‚คํ…์ฒ˜๋Š” ๊ทธ๋Œ€๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ๊ด€์ ์—์„œ ๋ณด๋ฉด, ๋ฐ์ดํ„ฐ(JSON)๋Š” ๋‹จ์ผ ์™•๋ณต ์š”์ฒญ์œผ๋กœ ๋„์ฐฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„ ์›Œํ„ฐํด์€ ์ด ๋ชจ๋ธ์—์„œ๋Š” ์ ˆ๋Œ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ž, ์ด๊ฒƒ์ด ์„œ๋ฒ„ ๋กœ๋”์˜ ์žฅ์ ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ๋‹จ์ ์€ ๋ฌด์—‡์ผ๊นŒ์š”?


์„œ๋ฒ„ ํ•จ์ˆ˜

์•ž์„œ ๋กœ๋”๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ์„ ๋•Œ, ์šฐ๋ฆฌ๋Š” ์ฝ”๋“œ ๊ทผ์ ‘์„ฑ์„ ํฌ๊ธฐํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด, ๋กœ๋”๋ฅผ ์„œ๋ฒ„์— ๋‚จ๊ฒจ๋‘๋ฉด์„œ ์ปดํฌ๋„ŒํŠธ๋งˆ๋‹ค ๋กœ๋”๋ฅผ ํ•˜๋‚˜์”ฉ ์ •์˜ํ•˜๋ฉด ์–ด๋–จ๊นŒ์š”? ๋‹ค์‹œ ๋งํ•ด ์ฝ”๋“œ ๊ทผ์ ‘์„ฑ์„ ๋˜์ฐพ๋Š” ๊ฒ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด์„  ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ ๊ฐ„์˜ ๊ฒฝ๊ณ„๋ฅผ ์ข€ ๋” ๋ชจํ˜ธํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์ผ๋‹จ ํ•œ๋ฒˆ ์‹œ๋„ํ•ด๋ณด๋ฉด์„œ ๊ฒฐ๊ณผ๋ฅผ ์ง€์ผœ๋ด…์‹œ๋‹ค.

์ด๊ฑธ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ• ์ง€๋Š” ์‚ฌ์šฉํ•˜๋Š” โ€œ๊ฒฝ๊ณ„ ํ๋ฆฌ๊ธฐโ€ ๋ฐฉ์‹์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ๋จผ์ € TanStack ์„œ๋ฒ„ ํ•จ์ˆ˜๋ฅผ ์˜ˆ๋กœ ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ด ๋ฐฉ์‹์€ ํด๋ผ์ด์–ธํŠธ์—์„œ ์ง์ ‘ importํ•  ์ˆ˜ ์žˆ๋Š” ์„œ๋ฒ„ ํ•จ์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

import { createServerFn } from '@tanstack/react-start';
import { loadPost, loadComments } from 'my-data-layer';

export const getPost = createServerFn({ method: 'GET' }).handler(async postId =>
  loadPost(postId)
);

export const getComments = createServerFn({
  method: 'GET',
}).handler(async postId => loadComments(postId));

๋˜ ๋‹ค๋ฅธ ์˜ˆ๋Š” ๋ฆฌ์•กํŠธ ์„œ๋ฒ„ ํ•จ์ˆ˜ ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

'use server';

import { loadPost, loadComments } from 'my-data-layer';

export async function getPost(postId) {
  return loadPost(postId);
}

export async function getComments(postId) {
  return loadComments(postId);
}

๋‘˜์˜ ์ฐจ์ด์— ๋Œ€ํ•ด ์ด ๊ธ€์—์„œ๋Š” ๊นŠ์ด ๋‹ค๋ฃจ์ง€ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ๋‘˜ ๋‹ค ์•”๋ฌต์ ์ธ RPC ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์š”์ ์€ ํด๋ผ์ด์–ธํŠธ ์ธก ์ปดํฌ๋„ŒํŠธ์—์„œ ์ง์ ‘ ์—”๋“œํฌ์ธํŠธ๋ฅผ importํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ตณ์ด REST ์—”๋“œํฌ์ธํŠธ๋‚˜ API ๋ผ์šฐํŠธ๋ฅผ ๋”ฐ๋กœ ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. import๋งŒ์œผ๋กœ ์•”๋ฌต์ ์ธ API ๋ผ์šฐํŠธ๊ฐ€ ๋˜๋Š” ์…ˆ์ด์ฃ .

์ด์ œ ์ฝ”๋“œ ๊ทผ์ ‘์„ฑ์ด ๋‹ค์‹œ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค! PostContent ์ปดํฌ๋„ŒํŠธ๋Š” getPost๋งŒ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

import { getPost } from './my-server-functions';import { Comments } from './Comments';

function usePostQuery(postId) {
  return useQuery(['post', postId], () => getPost(postId));}

function PostContent({ postId }) {
  const { data: post } = usePostQuery(postId);
  if (!post) {
    return null;
  }
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <Comments postId={postId} />
    </article>
  );
}

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Comments๋Š” ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ getComments๋ฅผ ์ง์ ‘ importํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { getComments } from './my-server-functions';
function usePostCommentsQuery(postId) {
  return useQuery(['post-comments', postId], () => getComments(postId));}

export function Comments({ postId }) {
  const { data: comments } = usePostCommentsQuery(postId);
  return (
    <ul className="comments">
      {comments.map(c => (
        <li key={c.id}>{c.text}</li>
      ))}
    </ul>
  );
}

๊ทธ๋Ÿฐ๋ฐ ์ž ๊น๋งŒ์š”โ€ฆ

์ด ๋ฐฉ์‹์€ ์•ž์„œ ์„ค๋ช…ํ•œ ๋ฌธ์ œ๋“ค์„ ํ•ด๊ฒฐํ•ด์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค!

์‚ฌ์‹ค, ์„ฑ๋Šฅ ์ธก๋ฉด์—์„œ ๋ณด๋ฉด ์ปดํฌ๋„ŒํŠธ๋‚˜ ์ฟผ๋ฆฌ ๋‚ด๋ถ€์—์„œ fetchํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋˜๋Œ์•„๊ฐ„ ์…ˆ์ž…๋‹ˆ๋‹ค. ์„œ๋ฒ„ ํ•จ์ˆ˜(Server Function)์˜ ์žฅ์ ์€ ๋” ๊น”๋”ํ•œ ๋ฌธ๋ฒ•(import๋ฅผ ํ†ตํ•œ ํ˜ธ์ถœ)๋ฟ์ด๊ณ , ์ด ๋ฐฉ์‹์„ ์ฝ”๋“œ์™€ ๊ทผ์ ‘ํ•œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ์— ์‚ฌ์šฉํ•˜๋ฉด ์„œ๋ฒ„ ๋กœ๋”๋ณด๋‹ค ์„ฑ๋Šฅ์ด ์˜คํžˆ๋ ค ํ‡ด๋ณดํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ํ•จ์ˆ˜๋Š” ๋‹จ์ผ ์™•๋ณต ์š”์ฒญ์„ ๊ฐ•์ œํ•˜์ง€๋„ ์•Š๊ณ , ํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„ ์›Œํ„ฐํด๋„ ๋ฐฉ์ง€ํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ํ•จ์ˆ˜๋Š” ์„œ๋ฒ„ ํ˜ธ์ถœ์„ ๋‹จ์ˆœํ™”ํ•ด์ฃผ์ง€๋งŒ, ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์ž์ฒด๋ฅผ ๊ฐœ์„ ํ•ด์ฃผ์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ๋Œ€์•ˆ์€ ๋ฌด์—‡์ผ๊นŒ์š”?


GraphQL ํ”„๋ž˜๊ทธ๋จผํŠธ

์•ˆํƒ€๊น๊ฒŒ๋„ ์˜คํ•ด๋ฐ›์•„์™”์ง€๋งŒ, GraphQL์€ ํšจ์œจ์ ์ธ ์ฝ”๋“œ ๊ทผ์ ‘์„ฑ์„ ์‹คํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ํ•˜๋‚˜์˜ ์ ‘๊ทผ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

GraphQL์˜ ๋ณธ๋ž˜ ์˜๋„๋Š” ๊ฐœ๋ณ„ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ์˜์กด์„ฑ์„ ํ”„๋ž˜๊ทธ๋จผํŠธ(fragment)๋กœ ์„ ์–ธํ•˜๊ณ , ์ด ํ”„๋ž˜๊ทธ๋จผํŠธ๋“ค์ด ํ•˜๋‚˜๋กœ ํ•ฉ์ณ์ง€๋„๋ก ๋งŒ๋“œ๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค. (์ˆ˜๋…„์ด ์ง€๋‚˜์„œ์•ผ Apollo์—์„œ๋„ ์ด๋ฅผ ์ œ๋Œ€๋กœ ์ง€์›ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.)

์ด ๋ฐฉ์‹์—์„œ๋Š” Comment ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์Šค์Šค๋กœ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function Comments({ comments }) {
  return (
    <ul className="comments">
      {comments.map(comment => (
        <Comment key={comment.id} comment={comment} />
      ))}
    </ul>
  );
}

function Comment({ comment }) {
  const data = useFragment(
    graphql`
      fragment CommentFragment on Comment {        id        text      }    `,
    comment
  );
  return <li>{data.text}</li>;
}

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ์ ์€ Comment ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ง์ ‘ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€๋Š” ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‹จ์ง€ ์–ด๋–ค ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•œ์ง€๋งŒ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. ์ด์ œ PostContent ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ดํŽด๋ด…์‹œ๋‹ค.

PostContent ์ปดํฌ๋„ŒํŠธ๋Š” Comment์˜ ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ์ž์‹ ์˜ ํ”„๋ž˜๊ทธ๋จผํŠธ์— ํฌํ•จ์‹œํ‚ต๋‹ˆ๋‹ค.

function PostContent({ post }) {
  const data = useFragment(
    graphql`
      fragment PostContentFragment on Post {
        title
        content
        comments {
          id
          ...CommentFragment        }
      }
    `,
    post
  );
  return (
    <article>
      <h1>{data.title}</h1>
      <p>{data.content}</p>
      <Comments comments={data.comments} />
    </article>
  );
}

์‹ค์ œ ๋ฐ์ดํ„ฐ ์š”์ฒญ์€ ์ƒ์œ„ ์ˆ˜์ค€ ์–ด๋”˜๊ฐ€์—์„œ ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค. ์ด ํ”„๋ž˜๊ทธ๋จผํŠธ๋“ค์€ ์ „์ฒด ๋ผ์šฐํŠธ๋ฅผ ์œ„ํ•œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ GraphQL ์ฟผ๋ฆฌ๋กœ ํ•ฉ์ณ์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

query PostPageQuery($postId: ID!) {
  post(id: $postId) {
    # PostContentFragment์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒƒ
    title
    content
    comments {
      # CommentFragment์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒƒ
      id
      text
    }
  }
}

์ด๋Š” ๋งˆ์น˜ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” ๋กœ๋”์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค!

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

๋ฌผ๋ก  GraphQL์ด ๋ชจ๋“  ์‚ฌ๋žŒ์—๊ฒŒ ์ ํ•ฉํ•œ ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ์ € ์—ญ์‹œ ์•„์ง ๋ฌธ๋ฒ•์ด ๋‹ค์†Œ ํ˜ผ๋ž€์Šค๋Ÿฝ๊ฒŒ ๋А๊ปด์งˆ ๋•Œ๊ฐ€ ์žˆ๊ณ (๋ถ€๋ถ„์ ์œผ๋กœ๋Š” ์ œ๊ฐ€ ์ด๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•ด๋ณด์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค), ์ด๊ฑธ ์ œ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์„œ๋ฒ„ ์ธก๊ณผ ํด๋ผ์ด์–ธํŠธ ์ธก ๋ชจ๋‘์—์„œ ์ผ์ • ์ˆ˜์ค€์˜ ์กฐ์ง์ ์ธ ์ดํ•ด๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ €๋Š” GraphQL์— ๋Œ€ํ•ด ์˜์—…์„ ํ•˜๋ ค๋Š” ๊ฒŒ ์•„๋‹™๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ GraphQL์ด ์ด ๋ฌธ์ œ๋ฅผ ์‹ค์ œ๋กœ ํ•ด๊ฒฐํ•œ ๋ช‡ ์•ˆ ๋˜๋Š” ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋ผ๋Š” ์ ์€ ๋ถ„๋ช…ํžˆ ์–ธ๊ธ‰ํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. GraphQL์€ ๋ฐ์ดํ„ฐ ์š”๊ตฌ์‚ฌํ•ญ์„ UI์™€ ๋‚˜๋ž€ํžˆ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋ฉด์„œ๋„, ๋‹จ์ˆœํ•˜๊ฒŒ ์ปดํฌ๋„ŒํŠธ๋‚˜ ์ฟผ๋ฆฌ ์•ˆ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ ‘๊ทผ ๋ฐฉ์‹์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋‹จ์ ์„ ํšŒํ”ผํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค(์ด๋Ÿฌํ•œ ๋‹จ์ ์€ ์„œ๋ฒ„ ํ•จ์ˆ˜์„ ์‚ฌ์šฉํ•˜๋“  ์•„๋‹ˆ๋“  ์กด์žฌํ•ฉ๋‹ˆ๋‹ค). ๋‹ค์‹œ ๋งํ•ด, GraphQL์€ ์„œ๋ฒ„ ๋กœ๋”์˜ ์„ฑ๋Šฅ ํŠน์„ฑ๊ณผ ์ฟผ๋ฆฌ์˜ ์ฝ”๋“œ ๊ทผ์ ‘์„ฑ๊ณผ ๋ชจ๋“ˆ์„ฑ์„ ๋ชจ๋‘ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด์™€ ์œ ์‚ฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋Š” ๋˜ ๋‹ค๋ฅธ ๋ฐฉ์‹์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

RSC

๋ฆฌ์•กํŠธ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋Š” 2010๋…„๋Œ€ ๋‚ด๋‚ด ๋ฆฌ์•กํŠธ ํŒ€์„ ๊ดด๋กญํ˜”๋˜ ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต์ž…๋‹ˆ๋‹ค. โ€œ๋ฆฌ์•กํŠธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ฐ€์ ธ์˜ฌ ๊ฒƒ์ธ๊ฐ€?โ€

๊ฐ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ๋งˆ๋‹ค ์ž์ฒด ์„œ๋ฒ„ ๋กœ๋”๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ์ƒํ•ด๋ณด์„ธ์š”. ์ปดํฌ๋„ŒํŠธ๋‹น ํ•˜๋‚˜์˜ ํ•จ์ˆ˜๊ฐ€ ๊ฐ€์žฅ ๋‹จ์ˆœํ•œ ํ•ด๊ฒฐ์ฑ…์ž…๋‹ˆ๋‹ค.

์ด์ œ ์šฐ๋ฆฌ๋Š” ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ ์„œ๋ฒ„ ๋กœ๋”๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์€ ํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„ ์›Œํ„ฐํด๋กœ ๋ฐ”๋กœ ๋Œ์•„๊ฐ€๋Š” ์‹ค์ˆ˜์ž„์„ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ๋ฐ˜๋Œ€๋กœ ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ๋กœ๋”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋Œ€์‹  ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

import { loadPost, loadComments } from 'my-data-layer';
import { PostContent, Comments } from './client';

function PostContentLoader({ postId }) {
  const post = await loadPost(postId);
  return (
    <PostContent post={post}>      <CommentsLoader postId={postId} />
    </PostContent>  );
}

function CommentsLoader({ postId }) {
  const comments = await loadComments(postId);
  return <Comments comments={comments} />;}
'use client';

export function PostContent({ post, children }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      {children}
    </article>
  );
}

export function Comments({ comments }) {
  return (
    <ul className="comments">
      {comments.map(c => (
        <li key={c.id}>{c.text}</li>
      ))}
    </ul>
  );
}

๋ฐ์ดํ„ฐ๋Š” ์œ„์—์„œ ์•„๋ž˜๋กœ ํ๋ฆ…๋‹ˆ๋‹ค. ์„œ๋ฒ„๊ฐ€ ์ง„์‹ค์˜ ์›์ฒœ(source of truth)์ž…๋‹ˆ๋‹ค. ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ํ”„๋กœํผํ‹ฐ๋ฅผ ๋ฐ›๊ณ ์ž ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” 'use client' ์ง€์‹œ์–ด๋ฅผ ํ†ตํ•ด ์ด๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ์˜ ์„œ๋ฒ„ ๋กœ๋”๋Š” ์ปดํฌ๋„ŒํŠธ์ฒ˜๋Ÿผ ์ƒ๊ฒผ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋“ค์„ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋ผ๊ณ  ๋ถ€๋ฅด์ง€๋งŒ, ์‹ค์ œ๋กœ๋Š” ๊ตฌ์„ฑ ๊ฐ€๋Šฅํ•œ ํ˜•ํƒœ์˜ ์„œ๋ฒ„ ๋กœ๋”์ธ ์…ˆ์ž…๋‹ˆ๋‹ค.

์ด ๊ตฌ์กฐ๋Š” ์˜ˆ์ „์˜ โ€œ์ปจํ…Œ์ด๋„ˆ vs ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ์ปดํฌ๋„ŒํŠธโ€ ํŒจํ„ด์„ ๋– ์˜ฌ๋ฆฌ๊ฒŒ ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์—ฌ๊ธฐ์„œ๋Š” ๋ชจ๋“  โ€œ์ปจํ…Œ์ด๋„ˆโ€๊ฐ€ ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜์–ด ์ถ”๊ฐ€์ ์ธ ์™•๋ณต ์š”์ฒญ์„ ๋ฐฉ์ง€ํ•œ๋‹ค๋Š” ์ฐจ์ด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์ ‘๊ทผ ๋ฐฉ์‹์œผ๋กœ ๋ฌด์—‡์„ ์–ป์„ ์ˆ˜ ์žˆ์„๊นŒ์š”?

  • ์šฐ๋ฆฌ๋Š” ์„œ๋ฒ„ ๋กœ๋”์˜ ํšจ์œจ์„ฑ์„ ์–ป์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„ ๋กœ๋”์— ์ ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ „๋žต(์š”์ฒญ๋‹น ์บ์‹ฑ, ์š”์ฒญ ๊ฐ„ ์บ์‹ฑ, ๋ฐ์ดํ„ฐ ์†Œ์Šค ๊ทผ์ฒ˜์— ์„œ๋ฒ„ ๋ฐฐ์น˜ ๋“ฑ)์ด ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—๋„ ๊ทธ๋Œ€๋กœ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„ ์›Œํ„ฐํด์€ ์ ˆ๋Œ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์œผ๋ฉฐ, ๋ฐ์ดํ„ฐ๋Š” ํ•ญ์ƒ ํ•œ ๋ฒˆ์˜ ์™•๋ณต ์š”์ฒญ์œผ๋กœ ๋„์ฐฉํ•ฉ๋‹ˆ๋‹ค.
  • ์šฐ๋ฆฌ๋Š” ์ปดํฌ๋„ŒํŠธ๋‚˜ GraphQL ํ”„๋ž˜๊ทธ๋จผํŠธ์˜ ์ฝ”๋“œ ๊ทผ์ ‘์„ฑ์„ ์–ป์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์˜์กด์„ฑ์ด ๋น„๋ก ์™„์ „ํžˆ ๊ฐ™์€ ํŒŒ์ผ์— ์„ ์–ธ๋˜์ง€๋Š” ์•Š๋”๋ผ๋„, ๋‹จ ํ•œ ๋‹จ๊ณ„๋งŒ ๋–จ์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฆฌ์•กํŠธ์—์„œ ํ”„๋กœํผํ‹ฐ์˜ ์ถœ์ฒ˜๋ฅผ ์ฐพ๋“ฏ์ด, โ€œ๋ชจ๋“  ์ฐธ์กฐ ์ฐพ๊ธฐโ€ ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ์„œ๋ฒ„ ํ”„๋กœํผํ‹ฐ์˜ ์ถœ์ฒ˜๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์šฐ๋ฆฌ๋Š” HTML ์•ฑ์˜ โ€œ๋ณธ์งˆ์ ์ธโ€ ์ •์‹  ๋ชจ๋ธ์„ ์–ป์Šต๋‹ˆ๋‹ค. ๋ณ„๋„์˜ โ€œAPIโ€๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉฐ(ํ•„์š”ํ•˜๋‹ค๋ฉด ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค), ์žฅ๊ธฐ์ ์ธ ์ •๊ทœํ™”๋œ ํด๋ผ์ด์–ธํŠธ ์บ์‹œ๋„ ์—†์Šต๋‹ˆ๋‹ค. ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ๋ผ๋Š” ํŒ”๋ ˆํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠธ๋ฆฌ๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋ฟ์ž…๋‹ˆ๋‹ค. ํŠน๋ณ„ํ•œ ์–ธ์–ด๋ฅผ ๋ฐฐ์›Œ์•ผ ํ•  ํ•„์š”๋„, ๋ณ„๋„์˜ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ API๋ฅผ ๋ฐฐ์›Œ์•ผ ํ•  ํ•„์š”๋„ ์—†์Šต๋‹ˆ๋‹ค. ์–ด์ฐŒ ๋ณด๋ฉด, ์•„์˜ˆ API ์ž์ฒด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ ์œ„ ์˜ˆ์ œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‹จ์ˆœํ™”ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

import { loadPost, loadComments } from 'my-data-layer';

async function PostContent({ postId }) {
  const post = await loadPost(postId);
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <Comments postId={postId} />
    </article>
  );
}

async function Comments({ postId }) {
  const comments = await loadComments(postId);
  return (
    <ul className="comments">
      {comments.map(c => (
        <li key={c.id}>{c.text}</li>
      ))}
    </ul>
  );
}

์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•  ๋•Œ(์ดˆ๊ธฐ ๋กœ๋”ฉ์ด๋“  ์ดํ›„ ๋‚ด๋น„๊ฒŒ์ด์…˜์ด๋“ ), ํด๋ผ์ด์–ธํŠธ๋Š” ์„œ๋ฒ„์— ๋‹จ์ผ ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์„œ๋ฒ„๋Š” <PostContent postId={123} />์—์„œ๋ถ€ํ„ฐ ์ถœ๋ ฅ์„ ์ง๋ ฌํ™”ํ•˜๊ธฐ ์‹œ์ž‘ํ•˜๋ฉฐ, ์ด๋ฅผ ์žฌ๊ท€์ ์œผ๋กœ ํŽผ์ณ์„œ ๋ฆฌ์•กํŠธ ํŠธ๋ฆฌ๋ฅผ ์ŠคํŠธ๋ฆฌ๋ฐํ•ฉ๋‹ˆ๋‹ค. ์ด ํŠธ๋ฆฌ๋Š” HTML๋กœ ๋ณ€ํ™˜๋˜๊ฑฐ๋‚˜ JSON์œผ๋กœ ์ง๋ ฌํ™”๋ฉ๋‹ˆ๋‹ค.

ํด๋ผ์ด์–ธํŠธ ์ž…์žฅ์—์„œ๋Š”, ๋ชจ๋“  ๋‚ด๋น„๊ฒŒ์ด์…˜์ด ์„œ๋ฒ„๋กœ์˜ ๋‹จ์ผ ์š”์ฒญ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์ž…์žฅ์—์„œ๋Š”, ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ๋กœ์ง์ด ํ•„์š”ํ•œ ๋งŒํผ ๋ชจ๋“ˆํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„๋Š” ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ํด๋ผ์ด์–ธํŠธ ํŠธ๋ฆฌ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.


๊ทธ๋ž˜์„œ ๋ฌด์—‡์„ ์–ป์—ˆ๋Š”๊ฐ€?

์ด ๊ธ€์—์„œ ํ•„์ž๋Š” RSC๊ฐ€ ๊ธฐ์กด์˜ ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ํŒจ์นญ ๋ฐฉ์‹๋“ค๊ณผ ์–ด๋–ค ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง€๋Š”์ง€๋ฅผ ์„ค๋ช…ํ•˜๊ณ ์ž ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฃจ์ง€ ๋ชปํ•œ ๋‚ด์šฉ๋„ ๋งŽ์ง€๋งŒ, ๋ช‡ ๊ฐ€์ง€๋งŒ ์–ธ๊ธ‰ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  • ๋ฐ์ดํ„ฐ ํŒจ์นญ์„ ํ•œ ๋ฒˆ์˜ ์™•๋ณต์œผ๋กœ ๋๋‚ด๋Š” ๊ฑด ๋А๋ฆฐ ๋ถ€๋ถ„์ด ์žˆ์„ ๊ฒฝ์šฐ ์•ˆ ์ข‹์•„ ๋ณด์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. (RSC๋Š” ์ŠคํŠธ๋ฆฌ๋ฐ์œผ๋กœ, GraphQL์€ @defer ์ง€์‹œ์–ด๋กœ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.)
  • ํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„ ์›Œํ„ฐํด์ด ํ”„๋ฆฌํŽ˜์นญ์œผ๋กœ ํ•ด๊ฒฐ๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. (ํ•˜์ง€๋งŒ ๊ทธ๊ฑด ์‚ฌ์‹ค์ด ์•„๋‹™๋‹ˆ๋‹ค. ๊ทผ๋ณธ์ ์œผ๋กœ ๋ฐœ์ƒํ•˜๋Š” ์›Œํ„ฐํด์€ ํ”„๋ฆฌํŽ˜์นญ์œผ๋กœ๋Š” ํ•ด๊ฒฐ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.)
  • ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฑด ์„œ๋ฒ„ ์ „์šฉ ์›Œํ„ฐํด ๋•Œ๋ฌธ์— ๋‚˜์œ ์„ ํƒ์ฒ˜๋Ÿผ ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ด๋Š” ๊ฒฝ์šฐ์— ๋”ฐ๋ผ ์‚ฌ์‹ค์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ๋ ˆ์ด์–ด๊ฐ€ ์ €์ง€์—ฐ์ด๋ผ๋ฉด ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์ด๋Š” RSC ์ž์ฒด์— ๋Œ€ํ•œ ๋ฐ˜๋ก ์ด๋ผ๊ธฐ๋ณด๋‹ค๋Š” ๋ณ„๋„์˜ ๋…ผ์˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.)

๋งˆ์ง€๋ง‰์œผ๋กœ ๊ฐ•์กฐํ•˜๊ณ  ์‹ถ์€ ๊ฒƒ์€, ์ฝ”๋“œ ๊ทผ์ ‘์„ฑ๊ณผ ํšจ์œจ์„ฑ์ด๋ผ๋Š” ๋‘ ๊ฐ€์ง€ ๋ฌธ์ œ๋ฅผ ๋™์‹œ์— ํ•ด๊ฒฐํ•˜๋ ค๋Š” ์ ‘๊ทผ์€ ํ”์น˜ ์•Š๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. HTML ํ…œํ”Œ๋ฆฟ์ด ๊ทธ๋ ‡๊ณ (Astro๊ฐ€ ๊ทธ ํ˜„๋Œ€์ ์ธ ๊ตฌํ˜„์ฒด ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค), GraphQL์ด ๊ทธ๋ ‡๊ณ , RSC๋„ ๊ทธ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค.

๋‹น์‹ ์ด ์ข‹์•„ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์—๊ฒŒ ๋ฌผ์–ด๋ณผ ์งˆ๋ฌธ์ด ํ•˜๋‚˜ ์ƒ๊ฒผ๋„ค์š”.


๐Ÿš€ ํ•œ๊ตญ์–ด๋กœ ๋œ ํ”„๋ŸฐํŠธ์—”๋“œ ์•„ํ‹ฐํด์„ ๋น ๋ฅด๊ฒŒ ๋ฐ›์•„๋ณด๊ณ  ์‹ถ๋‹ค๋ฉด Korean FE Article(https://kofearticle.substack.com/)์„ ๊ตฌ๋…ํ•ด์ฃผ์„ธ์š”!


[Ykss]
Written by@[Ykss]
๊ณ ์ด๊ฒŒ ๋‘์ง€ ์•Š๊ณ  ํ˜๋ ค๋ณด๋‚ด๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜์ž.

GitHubInstagramLinkedIn