๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

๐ŸŽˆ Chapter 6: ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง

ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ํ•œ๊ณ„โ€‹

๊ฒ€์ƒ‰ ์—”์ง„ ์ตœ์ ํ™”(SEO)โ€‹

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

์„ฑ๋Šฅโ€‹

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

ํด๋ผ์ด์–ธํŠธ ์ „์šฉ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋˜ ๋‹ค๋ฅธ ๋ฌธ์ œ๋Š” ๋„คํŠธ์›Œํฌ ํญํฌ ํ˜„์ƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์›น์‚ฌ์ดํŠธ๋‚˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋‹ค์šด๋กœ๋“œ, ํŒŒ์‹ฑ, ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋–„๋ฌธ์— ์ดˆ๊ธฐ ํŽ˜์ด์ง€ ๋กœ๋“œ๊ฐ€ ์ฐจ๋‹จ๋˜๋Š” ํ˜„์ƒ์„ ๋งํ•ฉ๋‹ˆ๋‹ค.

์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์„ ์‚ฌ์šฉํ•˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์œ ์šฉํ•œ ์ฝ˜ํ…์ธ ๋ฅผ ์ฆ‰์‹œ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ณด์•ˆโ€‹

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

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

์„œ๋ฒ„ ๋ Œ๋”๋ง์˜ ๋ถ€์ƒโ€‹

์„œ๋ฒ„ ๋ Œ๋”๋ง์˜ ์žฅ์ โ€‹

  1. ์ตœ์ดˆ ์˜๋ฏธ ์žˆ๋Š” ํŽ˜์ธํŠธ(first meaningful paint)๊ฐ€ ์™„์„ฑ๋˜๋Š” ์‹œ๊ฐ„์ด ๋” ๋นจ๋ผ์ง‘๋‹ˆ๋‹ค.
  • ์„œ๋ฒ„๊ฐ€ ์ดˆ๊ธฐ HTML ๋งˆํฌ์—…์„ ๋ Œ๋”๋งํ•ด ํด๋ผ์ด์–ธํŠธ๋กœ ์ „์†กํ•˜๋ฉด ๋ฐ”๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ Œ๋”๋ง๋˜๊ธฐ ์ „์— ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋‹ค์šด๋กœ๋“œ, ํŒŒ์‹ฑ, ์‹คํ–‰ํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์•ผ ํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง๊ณผ ๋Œ€์กฐ์ ์ž…๋‹ˆ๋‹ค.
  1. ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ ‘๊ทผ์„ฑ์„ ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค
  2. ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ SEO๋ฅผ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  3. ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ณด์•ˆ๋„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

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

ํ•˜์ด๋“œ๋ ˆ์ด์…˜โ€‹

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

ํด๋ผ์ด์–ธํŠธ ๋ฒˆ๋“ค ๋กœ๋”ฉ

  • ๋ธŒ๋ผ์šฐ์ €๋Š” ์ •์  HTML์„ ๋ Œ๋”๋งํ•˜๋Š” ๋™์•ˆ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ฝ”๋“œ๊ฐ€ ํฌํ•จ๋œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ฒˆ๋“ค์„ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ํŒŒ์‹ฑํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฒˆ๋“ค์—๋Š” ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ธฐ๋Šฅ์— ํ•„์š”ํ•œ ์ฝ”๋“œ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ถ”๊ฐ€

  • ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ฒˆ๋“ค์ด ๋กœ๋“œ๋˜๋ฉด ๋ฆฌ์•กํŠธ๋Š” ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋ฐ ๊ธฐํƒ€ ๋™์  ๊ธฐ๋Šฅ์„ DOM ์š”์†Œ์— ์ถ”๊ฐ€ํ•ด ์ •์  HTML์„ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋™์ž‘์€ ์ผ๋ฐ˜์ ์œผ๋กœ react-dom ํŒจํ‚ค์ง€์˜ hydrateRoot ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ์ˆ˜ํ–‰๋˜๋ฉฐ, ์ด ํ•จ์ˆ˜๋Š” ๋ฃจํŠธ ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ์™€ DOM ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›์Šต๋‹ˆ๋‹ค. ํ•˜์ด๋“œ๋ ˆ์ด์…˜์ด ์™„๋ฃŒ๋˜๋ฉด ์ •์  HTML์ด ์™„์ „ํ•œ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ๋ฆฌ์•กํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค.

ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์™„์ „ํžˆ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒํ•˜๊ฒŒ ๋ณ€ํ•ด์„œ ํ•„์š”์— ๋”ฐ๋ผ์„œ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์— ์‘๋‹ตํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , DOM์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ด๋“œ๋ ˆ์ด์…˜์— ๋Œ€ํ•œ ๋น„ํŒโ€‹

ํ•˜์ด๋“œ๋ ˆ์ด์…˜์€ ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง๋œ HTML์„ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์ด์ง€๋งŒ, ์ผ๋ถ€์—์„œ๋Š” ํ•˜์ด๋“œ๋ ˆ์ด์…˜์ด ํ•„์š” ์ด์ƒ์œผ๋กœ ๋А๋ฆฌ๋‹ค๊ณ  ๋น„ํŒํ•˜๋ฉฐ ์žฌ๊ฐœ ๊ฐ€๋Šฅ์„ฑ์„ ๋” ๋‚˜์€ ๋Œ€์•ˆ์œผ๋กœ ๊ผฝ๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

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

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

์„œ๋ฒ„ ๋ Œ๋”๋ง ์ž‘์„ฑโ€‹

ํด๋ผ์ด์–ธํŠธ ์ „์šฉ ๋ฆฌ์•กํŠธ ์•ฑ์— ์„œ๋ฒ„ ๋ Œ๋”๋ง์„ ์ˆ˜๋™์œผ๋กœ ์ถ”๊ฐ€ํ•˜๊ธฐโ€‹

// server.js

// ํ•„์š”ํ•œ ๋ชจ๋“ˆ ๊ฐ€์ ธ์˜ค๊ธฐ
const express = require("express");
const path = require("path");
const react = require("react");
// ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์„ ์œ„ํ•ด ReactDOMServer ๊ฐ€์ ธ์˜ค๊ธฐ
const ReactDOMServer = require("react-dom/server");

const App = require("./src/App");

const app = express();

// 'build' ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์žˆ๋Š” ์ •์  ํŒŒ์ผ ์„œ๋น™ํ•˜๊ธฐ
app.use(express.static(path.join(__dirname, "build")));

// ๋ชจ๋“  GET ์š”์ฒญ ์ฒ˜๋ฆฌ
app.get("*", (req, res) => {
// App ์ปดใ…ํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•ด HTML ๋ฌธ์ž์—ด์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
const html = ReactDOMServer.renderToString(<App />);

// ๋ Œ๋”๋ง๋œ App ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํฌํ•จ๋œ HTML ์‘๋‹ต์„ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>์˜ˆ์‹œ ๋ฆฌ์•กํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜</title>
</head>
<body>
<!-- ๋ Œ๋”๋ง๋œ App ์ปดํฌ๋„ŒํŠธ๋ฅผ ์—ฌ๊ธฐ์— ์‚ฝ์ž… -->
<div id="root">${html}</div>
<!-- ๋ฉ”์ธ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ฒˆ๋“ค์„ ์—ฐ๊ฒฐ -->
<script src="/static/js/main.js"></script>
</body>
</html>
`);
});

app.listen(3000, () => {
console.log("Server listening on port 3000");
})

์ด ์˜ˆ์‹œ์—์„œ๋Š” Express๋ฅผ ์‚ฌ์šฉํ•ด ./build ๋””๋ ‰ํ„ฐ๋ฆฌ์—์„œ ์ •์  ํŒŒ์ผ์„ ์„œ๋น„์Šคํ•˜๋Š” ์„œ๋ฒ„๋ฅผ ์ƒ์„ฑํ•œ ๋‹ค์Œ, ์„œ๋ฒ„์—์„œ ๋ฆฌ์•กํŠธ ์•ฑ์„ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ๋˜ ReactDOMServer๋ฅผ ์‚ฌ์šฉํ•ด ๋ฆฌ์•กํŠธ ์•ฑ์„ HTML ๋ฌธ์ž์—ด๋กœ ๋ Œ๋”๋งํ•œ ๋‹ค์Œ, ํด๋ผ์ด์–ธํŠธ๋กœ ์ „์†ก๋˜๋Š” ์‘๋‹ต์— ์‚ฝ์ž…ํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ด๋“œ๋ ˆ์ด์…˜โ€‹

ํ•˜์ด๋“œ๋ ˆ์ด์…˜์˜ ๋ชฉ์ ์€ ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง๋œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์™„์ „ํžˆ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

import React from "react";
import { hydrateRoot } from "react-dom/client";
import App from "./App";

hydrateRoot(document, <App />);

๋ฆฌ์•กํŠธ์˜ ์„œ๋ฒ„ ๋ Œ๋”๋ง APIโ€‹

renderToStringโ€‹

renderToString์€ ๋ฆฌ์•กํŠธ์—์„œ ์ œ๊ณตํ•˜๋Š” ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง API๋กœ, ์„œ๋ฒ„์—์„œ ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ HTML ๋ฌธ์ž์—ด๋กœ ๋ Œ๋”๋งํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. renderToString์€ ์„ฑ๋Šฅ, SEO, ์ ‘๊ทผ์„ฑ์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„์—์„œ ๋ฆฌ์•กํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ Œ๋”๋งํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

renderToString์€ ํ๋ฆ„์„ ๊ฐ€๋กœ๋ง‰๋Š” ๋™๊ธฐ์‹ API์ด๋ฏ€๋กœ ์‹คํ–‰์ด ์ค‘๋‹จ๋˜๊ฑฐ๋‚˜ ์ผ์‹œ์ ์œผ๋กœ ์ค‘์ง€๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋Œ€์ƒ์ด ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฃจํŠธ์—์„œ ๋ช‡ ๋‹จ๊ณ„ ๊นŠ์ด์— ์žˆ๋‹ค๋ฉด, ์ฒ˜๋ฆฌํ•˜๋Š” ์‹œ๊ฐ„์ด ์–ด๋А ์ •๋„ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

renderToString์€ ์œ ๋ฆฌํ•œ ์žฅ์ ์ด ๋งŽ์ง€๋งŒ ๋ถˆ๋ฆฌํ•œ ๋‹จ์ ๋„ ๋ช‡ ๊ฐ€์ง€ ์žˆ์Šต๋‹ˆ๋‹ค.

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

renderToPipeableStreamโ€‹

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

// server.js

// ํ•„์š”ํ•œ ๋ชจ๋“ˆ ๊ฐ€์ ธ์˜ค๊ธฐ
const express = require("express");
const path = require("path");
const react = require("react");
// ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์„ ์œ„ํ•ด ReactDOMServer ๊ฐ€์ ธ์˜ค๊ธฐ
const ReactDOMServer = require("react-dom/server");

const App = require("./src/App");

const app = express();

// 'build' ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์žˆ๋Š” ์ •์  ํŒŒ์ผ ์„œ๋น™ํ•˜๊ธฐ
app.use(express.static(path.join(__dirname, "build")));

// ๋ชจ๋“  GET ์š”์ฒญ ์ฒ˜๋ฆฌ
app.get("*", (req, res) => {
const { pipe } = ReactDOMServer.renderToPipeableStream(<App />, {
// ๋ฐ์ดํ„ฐ๋ฅผ ํŽ˜์น˜ํ•˜๊ธฐ ์ „์— ์•ฑ์ด ์ค€๋น„๋˜๋Š” ๊ฒฝ์šฐ
onShellReady: () => {
// ์„œ๋ฒ„๊ฐ€ HTML์„ ๋ณด๋‚ผ ๊ฑฐ๋ผ๊ณ  ํด๋ผ์ด์–ธํŠธ์— ํ†ต์ง€
res.setHeader("Content-Type", "text/html");
pipe(res); // ๋ฆฌ์•กํŠธ ์ŠคํŠธ๋ฆผ์˜ ์ถœ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ์‘๋‹ต ์ŠคํŠธ๋ฆผ์— ํŒŒ์ดํ”„
}
})
});

app.listen(3000, () => {
console.log("Server listening on port 3000");
})

renderToPipeableStream์˜ ๋ณ€ํ™˜ ๊ฒฐ๊ณผ๋Š” HTML ๋ฌธ์ž์—ด์ด ์•„๋‹ˆ๋ผ Node.js ์ŠคํŠธ๋ฆผ์ด ๋ฉ๋‹ˆ๋‹ค. ์ŠคํŠธ๋ฆผ์„ ์‚ฌ์šฉํ•˜๋ฉด ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ํ•œ๊บผ๋ฒˆ์— ์ฝ์–ด ๋“ค์ด๋Š” ๋Œ€์‹  ์ฒญํฌ ๋‹จ์œ„๋กœ ์ ์ง„์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์€ ๋ฉ”๋ชจ๋ฆฌ์— ๋ชจ๋‘ ์ฝ์–ด ๋“ค์ผ ์ˆ˜ ์—†๊ฑฐ๋‚˜ ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ํ•œ๋ฒˆ์— ์ „์†กํ•˜์ง€ ๋ชปํ•˜๋Š” ๋Œ€์šฉ๋Ÿ‰ ๋ฌธ์ž์—ด์ด๋‚˜ ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆผ์„ ๋‹ค๋ฃฐ ๋•Œ ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋ฆฌ์•กํŠธ๋Š” ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋งํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ฒซ ๋ฒˆ์งธ ๋ฐ”์ดํŠธ ์‹œ๊ฐ„(TTFB) ์ง€ํ‘œ๋ฅผ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์“ฐ๊ธฐ ๊ฐ€๋Šฅ ์ŠคํŠธ๋ฆผ์œผ๋กœ ์ŠคํŠธ๋ฆฌ๋ฐํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด HTML ๋งˆํฌ์—… ์ „์ฒด๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ๊นŒ์ง€ ์„œ๋ฒ„๊ฐ€ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ํด๋ผ์ด์–ธํŠธ์— ์ „์†กํ•˜๋Š” ๋Œ€์‹ , HTML ์‘๋‹ต ์ฒญํฌ๊ฐ€ ์ค€๋น„๋˜๋Š” ์ฆ‰์‹œ ์ „์†ก์„ ์‹œ์ž‘ํ•ด ์ „๋ฐ˜์ ์ธ ์ง€์—ฐ ์‹œ๊ฐ„์„ ์ค„์ž…๋‹ˆ๋‹ค.

renderToReadableStreamโ€‹

๋ธŒ๋ผ์šฐ์ € ์ŠคํŠธ๋ฆผ์€ ์›น ๋ธŒ๋ผ์šฐ์ € ๋‚ด์˜ ํด๋ผ์ด์–ธํŠธ ํ™˜๊ฒฝ์—์„œ ์ž‘๋™ํ•˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ์œผ๋ฉฐ, ์ฃผ๋กœ ๋„คํŠธ์›Œํฌ ์š”์ฒญ, ๋ฏธ๋””์–ด ์ŠคํŠธ๋ฆฌ๋ฐ, ๋ธŒ๋ผ์šฐ์ €์˜ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์ž‘์—…์—์„œ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ € ์ŠคํŠธ๋ฆผ์€ ์›น ์ „์ฒด์˜ API ํ‘œ์ค€ํ™”๋ฅผ ๋ชฉํ‘œ๋กœ ํ•˜๋Š” WHATWG์—์„œ ์ •์˜ํ•œ ์ŠคํŠธ๋ฆผ ํ‘œ์ค€์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. Node.js ์ŠคํŠธ๋ฆผ๊ณผ ๋‹ฌ๋ฆฌ ๋ธŒ๋ผ์šฐ์ € ์ŠคํŠธ๋ฆผ์€ read(), write(), pipeThrough() ๋“ฑ์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ์˜ ํ๋ฆ„์„ ์ œ์–ดํ•˜๊ณ  ์ŠคํŠธ๋ฆฌ๋ฐ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋˜ ๋ณด๋‹ค ํ‘œ์ค€ํ™”๋œ ํ”„๋ผ๋ฏธ์Šค ๊ธฐ๋ฐ˜ API๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Node.js ์ŠคํŠธ๋ฆผ๊ณผ ๋ธŒ๋ผ์šฐ์ € ์ŠคํŠธ๋ฆผ์€ ๋ชจ๋‘ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ๋ชฉ์ ์œผ๋กœ ํ•˜์ง€๋งŒ, ๋™์ž‘ํ•˜๋Š” ํ™˜๊ฒฝ์ด ๋‹ค๋ฅด๋ฉฐ ๊ฐ ํ™˜๊ฒฝ์ด ๋”ฐ๋ฅด๋Š” API์™€ ํ‘œ์ค€๋„ ์กฐ๊ธˆ์”ฉ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. Node.js ์ŠคํŠธ๋ฆผ์€ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜์ด๋ฉฐ ์„œ๋ฒ„ ์ธก ์ž‘์—…์— ์ ํ•ฉํ•œ ๋ฐ˜๋ฉด, ๋ธŒ๋ผ์šฐ์ € ์ŠคํŠธ๋ฆผ์€ ์ตœ์‹  ์›น ํ‘œ์ค€์— ๋”ฐ๋ผ ํ”„๋ผ๋ฏธ์Šค ๊ธฐ๋ฐ˜์ด๋ฉฐ ํด๋ผ์ด์–ธํŠธ ์ธก ์ž‘์—…์— ๋งž๊ฒŒ ์กฐ์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋‘ ํ™˜๊ฒฝ ๋ชจ๋‘ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด ๋ฆฌ์•กํŠธ์—๋Š” Node.js ์ŠคํŠธ๋ฆผ์šฉ renderToPipeableStream๊ณผ ๋ธŒ๋ผ์šฐ์ € ์ŠคํŠธ๋ฆผ์šฉ renderToReadableStream ํ•จ์ˆ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. renderToReadableStream API๋Š” renderToPipeableStream๊ณผ ์œ ์‚ฌํ•˜์ง€๋งŒ Node.js ๋„ค์ดํ‹ฐ๋ธŒ ์ŠคํŠธ๋ฆผ ๋Œ€์‹  ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.