์ฃผ์˜ํ•˜์„ธ์š”!

renderToString์€ ์ŠคํŠธ๋ฆฌ๋ฐ์ด๋‚˜ ๋ฐ์ดํ„ฐ ๋Œ€๊ธฐ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์•ˆ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.

renderToString์€ React ํŠธ๋ฆฌ๋ฅผ HTML ๋ฌธ์ž์—ด๋กœ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

const html = renderToString(reactNode, options?)

๋ ˆํผ๋Ÿฐ์Šค

renderToString(reactNode, options?)

์„œ๋ฒ„์—์„œ renderToString์„ ์‹คํ–‰ํ•˜๋ฉด ์•ฑ์„ HTML๋กœ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

import { renderToString } from 'react-dom/server';

const html = renderToString(<App />);

ํด๋ผ์ด์–ธํŠธ์—์„œ hydrateRoot๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์„œ๋ฒ„์—์„œ ์ƒ์„ฑ๋œ HTML์„ ์ƒํ˜ธ์ž‘์šฉํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

์•„๋ž˜ ์˜ˆ์‹œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

๋งค๊ฐœ๋ณ€์ˆ˜

  • reactNode: HTML๋กœ ๋ Œ๋”๋งํ•  React ๋…ธ๋“œ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด <App />๊ณผ ๊ฐ™์€ JSX ๋…ธ๋“œ์ž…๋‹ˆ๋‹ค.
  • optional options: ์„œ๋ฒ„ ๋ Œ๋”๋ง์„ ์œ„ํ•œ ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.
    • optional identifierPrefix: useId์— ์˜ํ•ด ์ƒ์„ฑ๋œ ID์— ๋Œ€ํ•ด React๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๋ฌธ์ž์—ด ์ ‘๋‘์‚ฌ์ž…๋‹ˆ๋‹ค. ๊ฐ™์€ ํŽ˜์ด์ง€์—์„œ ์—ฌ๋Ÿฌ ๋ฃจํŠธ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ถฉ๋Œ์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. hydrateRoot์— ์ „๋‹ฌ๋œ ์ ‘๋‘์‚ฌ์™€ ๋™์ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ฐ˜ํ™˜๊ฐ’

HTML ๋ฌธ์ž์—ด.

์ฃผ์˜ ์‚ฌํ•ญ

  • renderToString๋Š” Suspense ์ง€์›์— ํ•œ๊ณ„๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ค‘๋‹จ๋œ๋‹ค๋ฉด renderToString๋Š” ์ฆ‰์‹œ ํ•ด๋‹น ํด๋ฐฑ์„ HTML๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

  • renderToString์€ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋™์ž‘ํ•˜์ง€๋งŒ, ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.


์‚ฌ์šฉ๋ฒ•

React ํŠธ๋ฆฌ๋ฅผ HTML ๋ฌธ์ž์—ด๋กœ ๋ Œ๋”๋งํ•˜๊ธฐ

์„œ๋ฒ„ ์‘๋‹ต๊ณผ ํ•จ๊ป˜ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š” HTML ๋ฌธ์ž์—ด๋กœ ์•ฑ์„ ๋ Œ๋”๋งํ•˜๋ ค๋ฉด renderToString์„ ํ˜ธ์ถœํ•˜์„ธ์š”.

import { renderToString } from 'react-dom/server';

// ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ๊ตฌ๋ฌธ์€ ๋ฐฑ์—”๋“œ ํ”„๋ ˆ์ž„์›Œํฌ์— ๋”ฐ๋ผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค
app.use('/', (request, response) => {
const html = renderToString(<App />);
response.send(html);
});

์ด๋Š” React ์ปดํฌ๋„ŒํŠธ์˜ ์ดˆ๊ธฐ ์ƒํ˜ธ์ž‘์šฉํ•˜์ง€ ์•Š๋Š” HTML ์ถœ๋ ฅ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„์—์„œ ์ƒ์„ฑ๋œ HTML์„ Hydrateํ•˜์—ฌ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก hydrateRoot๋ฅผ ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์˜ํ•˜์„ธ์š”!

renderToString์€ ์ŠคํŠธ๋ฆฌ๋ฐ ๋˜๋Š” ๋ฐ์ดํ„ฐ ๋Œ€๊ธฐ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์•ˆ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.


๋Œ€์•ˆ

์„œ๋ฒ„์—์„œ renderToString์„ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ Œ๋”๋ง์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜

renderToString์€ ๋ฌธ์ž์—ด์„ ์ฆ‰์‹œ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ, ๋กœ๋”ฉ ์ค‘์ธ ์ฝ˜ํ…์ธ ๋ฅผ ์ŠคํŠธ๋ฆฌ๋ฐํ•˜๋Š” ๊ฒƒ์„ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ฐ€๋Šฅํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์™„์ „ํ•œ ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ˜ ๋Œ€์•ˆ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

  • Node.js๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ renderToPipeableStream์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • Deno์™€ ์ตœ์‹  ์—ฃ์ง€ ๋Ÿฐํƒ€์ž„์—์„œ Web Stream์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ renderToReadableStream์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

์„œ๋ฒ„ ํ™˜๊ฒฝ์—์„œ ์ŠคํŠธ๋ฆผ์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋„ renderToString์„ ๊ณ„์† ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


์„œ๋ฒ„์—์„œ renderToString์„ ์ •์  ํ”„๋ฆฌ๋ Œ๋”๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜

renderToString์€ ๋ฌธ์ž์—ด์„ ์ฆ‰์‹œ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ, ์ •์  HTML ์ƒ์„ฑ์„ ์œ„ํ•ด ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์„ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ฐ€๋Šฅํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์™„์ „ํ•œ ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ˜ ๋Œ€์•ˆ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

  • Node.js๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ prerenderToNodeStream์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • Deno์™€ ์ตœ์‹  ์—ฃ์ง€ ๋Ÿฐํƒ€์ž„์—์„œ Web Streams์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ prerender๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

์ •์  ์‚ฌ์ดํŠธ ์ƒ์„ฑ ํ™˜๊ฒฝ์—์„œ ์ŠคํŠธ๋ฆผ์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋Š” renderToString์„ ๊ณ„์† ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์—์„œ renderToString ์ œ๊ฑฐํ•˜๊ธฐ

ํด๋ผ์ด์–ธํŠธ์—์„œ ์ผ๋ถ€ ์ปดํฌ๋„ŒํŠธ๋ฅผ HTML๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด renderToString์„ ์‚ฌ์šฉํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

// ๐Ÿšฉ ๋ถˆํ•„์š”: ํด๋ผ์ด์–ธํŠธ์—์„œ renderToString ์‚ฌ์šฉํ•˜๊ธฐ
import { renderToString } from 'react-dom/server';

const html = renderToString(<MyIcon />);
console.log(html); // ์˜ˆ๋ฅผ ๋“ค์–ด, "<svg>...</svg>"

ํด๋ผ์ด์–ธํŠธ์—์„œ react-dom/server๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋ฒˆ๋“ค ํฌ๊ธฐ๊ฐ€ ์ปค์ง€๋ฏ€๋กœ ํ”ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ผ๋ถ€ ์ปดํฌ๋„ŒํŠธ๋ฅผ HTML๋กœ ๋ Œ๋”๋งํ•ด์•ผ ํ•  ๊ฒฝ์šฐ createRoot๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  DOM์—์„œ HTML์„ ์ฝ์œผ์„ธ์š”.

import { createRoot } from 'react-dom/client';
import { flushSync } from 'react-dom';

const div = document.createElement('div');
const root = createRoot(div);
flushSync(() => {
root.render(<MyIcon />);
});
console.log(div.innerHTML); // ์˜ˆ๋ฅผ ๋“ค์–ด, "<svg>...</svg>"

flushSync ํ˜ธ์ถœ์€ innerHTML ์†์„ฑ์„ ์ฝ๊ธฐ ์ „์— DOM์„ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.


๋ฌธ์ œ ํ•ด๊ฒฐ

์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ผ์‹œ ์ค‘๋‹จ๋˜๋ฉด HTML์— ํ•ญ์ƒ ํด๋ฐฑ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

renderToString์€ Suspense๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ผ๋ถ€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ผ์‹œ ์ค‘๋‹จSuspend๋˜๊ฑฐ๋‚˜ (์˜ˆ๋ฅผ ๋“ค์–ด, lazy์™€ ํ•จ๊ป˜ ์ •์˜๋˜๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ) renderToString์€ ์ฝ˜ํ…์ธ ๊ฐ€ ํ•ด๊ฒฐ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. renderToString๋Š” ๊ทธ ์œ„์— ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด <Suspense> ๊ฒฝ๊ณ„๋ฅผ ์ฐพ์•„ fallback ํ”„๋กœํผํ‹ฐ๋ฅผ HTML์— ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ๋‚ด์šฉContent์€ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ๊ฐ€ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

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