react-snap โœจ๐Ÿง™๐Ÿ’จ์œผ๋กœ React ์‚ฌ๋ผ์ง€๋Š” ํ–‰์œ„ ์ˆ˜ํ–‰

5622 ๋‹จ์–ด webdevjavascriptreact
React์—์„œ ํ”„๋ŸฐํŠธ์—”๋“œ๋ฅผ ๊ตฌ์ถ•ํ•œ ๋‹ค์Œ ์„œ๋ฒ„ ์—†์ด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํ”์  ์—†์ด HTML๋กœ ์ปดํŒŒ์ผ ๋ฐ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋งํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”?



์™€ํ•˜ํ•˜ํ•˜!

๋ชจ๋“  ์ •์  ๋น„๋Œ€ํ™”ํ˜• ํŽ˜์ด์ง€์— ๋Œ€ํ•ด ๋งค์šฐ ์‰ฝ๊ฒŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค!

SSR & ๋ฆฌ์•กํŠธ์Šค๋ƒ… ๐Ÿ‘



์„œ๋ฒ„์ธก ๋ Œ๋”๋ง(SSR)์€ ์ƒˆ๋กœ์šด ๊ฐœ๋…์ด ์•„๋‹™๋‹ˆ๋‹ค. ์›น ์‚ฌ์ดํŠธ, ํŠนํžˆ ํŠธ๋ž˜ํ”ฝ์ด ๋งŽ์€ ์›น ์‚ฌ์ดํŠธ๋Š” ํŽ˜์ด์ง€ ๋กœ๋“œ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์ˆ˜๋…„ ๋™์•ˆ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ด ์™”์Šต๋‹ˆ๋‹ค. ๋น ๋ฅธ ์ฝ๊ธฐ ๊ฒฝํ—˜์ด ๊ฐ€์žฅ ์ค‘์š”ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— GMG/The Onion์— ์žˆ์„ ๋•Œ ์ด๊ฒƒ์€ ํ•ญ์ƒ ์ตœ์ „์„ ์— ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

React-land์˜ SSR์€ ์ผ๋ฐ˜์ ์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค(react-snapreadme์—์„œ ๊ฐ€์ ธ์˜ด):

import { hydrate, render } from "react-dom";

const rootElement = document.getElementById("root");
if (rootElement.hasChildNodes()) {
  hydrate(<App />, rootElement);
} else {
  render(<App />, rootElement);
}


์„œ๋ฒ„๋Š” ์ดˆ๊ธฐrender๋ฅผ ์ˆ˜ํ–‰ํ•œ ๋‹ค์Œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ด๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ๊ทธ๋ƒฅ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹คhydrate. ๊ทธ๋Ÿฌ๋‚˜ ๋•Œ๋•Œ๋กœ ๋‚˜๋Š” ์›ํ•˜์ง€ ์•Š๊ณ hydrate ๋Œ€์‹  ๋‚˜๋ฅผ ๊ฑฐ๊ธฐ์— ์žˆ๊ฒŒ ํ•œ ์ฝ”๋“œ๋ฅผ ์™„์ „ํžˆ ํฌ๊ธฐํ•˜๋Š” ๊ฒƒ์„ ์„ ํ˜ธํ•ฉ๋‹ˆ๋‹ค.

์šด ์ข‹๊ฒŒ๋„ "์ œ๋กœ ๊ตฌ์„ฑ ํ”„๋ ˆ์ž„์›Œํฌ์— ๊ตฌ์• ๋ฐ›์ง€ ์•Š๋Š” ์ •์  ์‚ฌ์ „ ๋ Œ๋”๋ง"๋„๊ตฌ๊ฐ€ ์ด๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค! react-snap์€ puppeteer(ํ—ค๋“œ๋ฆฌ์Šค Chrome)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ƒ์œผ๋กœ ๋ Œ๋”๋งํ•œ ๋‹ค์Œ ๋ช‡ ๊ฐ€์ง€ ์ถ”๊ฐ€ ์ตœ์ ํ™”๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๋นŒ๋“œ ์Šคํฌ๋ฆฝํŠธ(์ด๋ฆ„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  React๋ฟ๋งŒ ์•„๋‹ˆ๋ผ)๊ฐ€ ์žˆ๋Š” ๋ชจ๋“  ํ”„๋กœ์ ํŠธ์— ํŒจํ‚ค์ง€๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  postbuild ์Šคํฌ๋ฆฝํŠธ๋กœ ์ถ”๊ฐ€ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค.

"scripts": {
  "postbuild": "react-snap"
}


๊ทธ๋ฆฌ๊ณ  ์ด๊ฒƒ์€ ์šฐ๋ฆฌ์—๊ฒŒ ์ ˆ๋ฐ˜์˜ ๊ธธ์„ ๊ฐ€์ ธ๋‹ค์ค๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฌด์ž์‚ฐ์œผ๋กœ ๋งŒ๋“ค๋ ค๋ฉด ๋ช‡ ๊ฐ€์ง€ ๋‹ค๋ฅธ ํ•ญ๋ชฉflags๋„ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

"reactSnap": {
  "inlineCss": true,
  "removeBlobs": true,
  "removeScriptTags": true,
  "removeStyleTags": true
}


Note: inlineCss is still an experimental feature and doesn't currently work because of a service worker issue. There's a patched version that includes a fix.



์ด์ œ build ๋ฅผ ์‹คํ–‰ํ•œ ํ›„ react-snap ๋ฐ minimalcss๋Š” ๊ฐ€์ƒ DOM์„ ์‹ค์ œ DOM์œผ๋กœ ๋ฐ”๊พธ๊ณ  ๋ชจ๋“  ์ž์‚ฐ์„ ์ œ๊ฑฐํ•œ ๋‹ค์Œ ํ‰๋ฒ”ํ•œ ๊ธฐ์กด HTML์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค!

์ด๊ฒƒ์ด ๋ฉ‹์ง„ ์ด์œ  ๐Ÿ”ฎ


  • ์‹ค์ œ ์„œ๋ฒ„ ์—†์ด๋„ SSR์˜ ์žฅ์ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ๋Š” HTML ๋งˆํฌ์—…์„ ๊ตฌ๋ฌธ ๋ถ„์„ํ•˜๊ณ  ์™„๋ฃŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฝํŠธ ์‹œ๊ฐ„, ์ž์‚ฐ ๋กœ๋“œ ๋“ฑ์ด ์—†์Œ

  • ์Šคํƒ€์ผ์ด ์ธ๋ผ์ธ๋˜๋ฏ€๋กœ ์Šคํƒ€์ผ์‹œํŠธ ํ˜ธ์ŠคํŒ…์— ๋Œ€ํ•ด ๊ฑฑ์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  • ๋˜๋Š” ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋งํฌํ•ฉ๋‹ˆ๋‹ค.
  • ์›ํ•˜๋Š” CSS-in-JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. ํด๋ผ์ด์–ธํŠธ์— ๋กœ๋“œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  • ๋ชจ๋“  ์–ด๋ ค์šด ์ž‘์—…์€ ํ•œ ๋ฒˆ๋งŒ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ์กด SSR์„ ์‚ฌ์šฉํ•œ ์บ์‹ฑ๋„ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๋ณ€๊ฒฝ ์‹œ ์บ์‹œ ๋ฌดํšจํ™”์— ๋Œ€ํ•ด ๊ฑฑ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • ์•ฑ SEO๋„ ๋ฌด๋ฃŒ๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ์›น ํฌ๋กค๋Ÿฌ๋Š” ๋ฉ‹์ง„ React ๋ฒˆ๋“ค์— ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š๊ณ  ๊ทธ๋“ค์ด ๋ณผ ์ˆ˜ ์žˆ๋Š” HTML ๋งˆํฌ์—…๋งŒ ์‹ ๊ฒฝ์”๋‹ˆ๋‹ค.




  • ๊ฐœ๋Š” ์—†๊ณ  ์ฝง๋ฌผ๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค.

    ๋ชจ๋“  ์‚ฌ์šฉ ์‚ฌ๋ก€์— ํ•ด๋‹น๋˜์ง€ ์•Š์Œ



    ๋ถ„๋ช…ํžˆ ์ด ์ ‘๊ทผ ๋ฐฉ์‹์€ ํŽ˜์ด์ง€์—์„œ ์ƒํ˜ธ ์ž‘์šฉ์ด ํ•„์š”ํ•  ๋•Œ ๋ฌด๋„ˆ์ง‘๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒƒ์€ ์ •์ ์ธ ์ฝ๊ธฐ ์ „์šฉ ํŽ˜์ด์ง€(์˜ˆ: ๋ธ”๋กœ๊ทธ ๊ธฐ์‚ฌ)๋ฅผ ๋ Œ๋”๋งํ•˜๊ณ  ๋‹ค๋ฅธ ๋ถ€ํ’€๋ฆผ์ด ํ•„์š”ํ•˜์ง€ ์•Š์„ ๋•Œ ๋น›์„ ๋ฐœํ•ฉ๋‹ˆ๋‹ค.


    ์ด๊ฒƒ์„ CI์™€ ํ•จ๊ป˜ ์ ์šฉํ•œ ํ”„๋กœ์ ํŠธ๋ฅผ ๊ณต์œ ํ•  ๋˜ ๋‹ค๋ฅธ ๊ฒŒ์‹œ๋ฌผ์„ ๊ธฐ๋Œ€ํ•ด์ฃผ์„ธ์š”!

    ์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ