๐Ÿ’ป์šฉ๋‡ฝ ๊ฐœ๋ฐœ ๋…ธํŠธ๐Ÿ’ป
article thumbnail
๋ฐ˜์‘ํ˜•

ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ๊ฐ€๋Šฅํ•œ N๊ฐ€์ง€ ๋ฐฉ๋ฒ•(feat.React)

๐Ÿ“– ๋“ค์–ด๊ฐ€๋ฉฐ

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

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


์ตœ์ ํ™”๋ฅผ ์ง„ํ–‰ํ•˜๊ธฐ ์ „์—๋Š” ๋ธŒ๋ผ์šฐ์ € ๊ฐœ๋ฐœ์ž ๋„๊ตฌ > ์„ฑ๋Šฅ ํƒญ ๋˜๋Š” ๋„คํŠธ์›Œํฌ ํƒญ, Lighthouse, React-Dev-Tools์™€ ๊ฐ™์€ ์„ฑ๋Šฅ ์ธก์ •์„ ํ†ตํ•ด์„œ ์ „๊ณผ ํ›„ ๋น„๊ต๋ฅผ ํ†ตํ•ด์„œ ์–ผ๋งˆ๋‚˜ ๊ฐœ์„ ์ด ๋˜์—ˆ๋Š”์ง€ ์ˆ˜์น˜๋กœ ํ™•์ธํ•ด๋ณด์‹œ๊ธธ ์ ๊ทน ์ถ”์ฒœ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

N๊ฐ€์ง€ ์ตœ์ ํ™” ๋ฐฉ๋ฒ•๋“ค

๐Ÿ–ผ๏ธ ์ด๋ฏธ์ง€ ์ง€์—ฐ ๋กœ๋”ฉ(Lazy Loading)

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


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

Intersection Observer API๋‚˜ scroll event๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€์˜ ๊ฐ€์‹œ์„ฑ์„ ๊ฐ์ง€ํ•˜์—ฌ ์ด๋ฏธ์ง€ ๋ฆฌ์†Œ์Šค๋ฅผ ์š”์ฒญํ•˜๋Š” ๋ฐฉ๋ฒ•, ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ณธ ์ง€์› ์†์„ฑ์ธ loading="lazy"๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋“ค์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ์‹œ๋ฅผ ์•Œ์•„๋ณผ๊ฒŒ์š”.

์ด๋ฏธ์ง€ ์Šฌ๋ผ์ด๋”๊ฐ€ ํ•„์š”ํ•œ ์ƒํ™ฉ์ด๋ผ๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค.

์ ์šฉ ์ „

// ์ง€์—ฐ ๋กœ๋”ฉ ์ ์šฉ โŒ
{images.map((imageUrl, index) => (
	<SwiperSlide key={index}>
		<img
			src={imageUrl}
			alt={`${index} + ์ด๋ฏธ์ง€}`}
		/>
	</SwiperSlide>
))}

์ด๋ฏธ์ง€ ์ง€์—ฐ๋กœ๋”ฉ์„ ์ ์šฉํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” ๋ณด์ด์ง€ ์•Š๋Š” ์˜์—ญ์˜ ์ด๋ฏธ์ง€๋„ ๋ชจ๋‘ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ ์šฉ ํ›„

// ์ง€์—ฐ ๋กœ๋”ฉ ์ ์šฉ โœ…
{images.map((imageUrl, index) => (
	<SwiperSlide key={index}>
		<img
			src={imageUrl}
			alt={`${index} + ์ด๋ฏธ์ง€}`}
            		loading="lazy"
		/>
	</SwiperSlide>
))}

์‹ค์ œ๋กœ ํ™”๋ฉด ์˜์—ญ์— ๋ณด์ผ ๋•Œ ์ด๋ฏธ์ง€ ๋ฆฌ์†Œ์Šค๋ฅผ ์š”์ฒญํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

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

๋ธŒ๋ผ์šฐ์ €์—์„œ ์ž์ฒด์ ์œผ๋กœ ์ง€์›ํ•˜๋Š” loading="lazy"๋Š” ์ฝ”๋“œ ํ•œ ์ค„๋กœ ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€๋งŒ ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ ์ธก๋ฉด์—์„œ๋Š” ๊ธฐ๋Šฅ์ด ์ œํ•œ์ ์ž…๋‹ˆ๋‹ค.
UX๋ฅผ ๊ณ ๋ คํ•˜์‹œ๋Š” ๊ฐœ๋ฐœ์ž ๋ถ„๋“ค์ด๋ผ๋ฉด! fallbackUI๋ฅผ ๊ณ ๋ คํ•ด ๋ณด์„ธ์š”.

๊ทธ ๋ฐฉ๋ฒ•์œผ๋กœ  ๊ฐ„๋‹จํ•œ ๊ฒฝ์šฐ์—๋Š” CSS์™€ placeholder ์ด๋ฏธ์ง€๋ฅผ ์ ์ ˆํ•˜๊ฒŒ ์กฐํ•ฉํ•ด์„œ ๋ณด์—ฌ์ฃผ๊ฑฐ๋‚˜, Intersection ObserverAPI๋ฅผ ์ง์ ‘ ํ™œ์šฉํ•˜์—ฌ ์ปค์Šคํ…€ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜, ์ด๋ฏธ์ง€ ์ง€์—ฐ ๋กœ๋”ฉ ๊ด€๋ จ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ณด๋‹ค ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌ๋ฅผ ์‹œ๋„ํ•ด๋ณผ ์ˆ˜ ์žˆ์–ด์š”.

Trade-off

  • ๋ธŒ๋ผ์šฐ์ € ๋„ค์ดํ‹ฐ๋ธŒ lazy ํ™œ์šฉ โžก๏ธ ์ฝ”๋“œ ํ•œ ์ค„๋กœ ๊ตฌํ˜„ ๊ฐ€๋Šฅ, ๋ธŒ๋ผ์šฐ์ € ๋‚ด์žฅ ์ตœ์ ํ™”๋กœ ์„ฑ๋Šฅ ์šฐ์ˆ˜, fallbackUI๋Š” ๋ณ„๋„ ๊ตฌํ˜„ ํ•„์š”
  • Intersection Observer API ํ™œ์šฉ โžก๏ธ ์„ธ๋ฐ€ํ•œ ์ œ์–ด ๊ฐ€๋Šฅ, ๋‹ค์–‘ํ•œ ๋กœ๋”ฉ ์ „๋žต ๊ตฌํ˜„ ๊ฐ€๋Šฅ, ์ ์ ˆํ•œ ์„ฑ๋Šฅ, ์ฝ”๋“œ์–‘ ์ฆ๊ฐ€
  • ๊ด€๋ จ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ โžก๏ธ ๋กœ๋”ฉ ์ง€์—ฐ๊ณผ fallbackUI ๊ธฐ๋Šฅ ํ†ตํ•ฉ ์ œ๊ณต, ๋น ๋ฅธ ๊ฐœ๋ฐœ ๊ฐ€๋Šฅ,  ์˜์กด์„ฑ ์ถ”๊ฐ€

๊ฐ์ž ์ƒํ™ฉ์— ๋งž๋Š” ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•˜์‹œ๋ฉด ๋  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๐Ÿ—œ๏ธ ์ด๋ฏธ์ง€ ์••์ถ• ๋ฐ ์‚ฌ์ด์ฆˆ ์กฐ์ ˆ

์ด๋ฏธ์ง€ ์••์ถ• ๋ฐ ์‚ฌ์ด์ฆˆ ์กฐ์ ˆ์€ ์›น์‚ฌ์ดํŠธ์˜ ์ด๋ฏธ์ง€ ํŒŒ์ผ ํฌ๊ธฐ๋ฅผ ์ตœ์ ํ™”ํ•˜๋Š” ์ข…ํ•ฉ์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
์ ์ ˆํ•œ ์ด๋ฏธ์ง€ ํ˜•์‹ ์„ ํƒ(JPEG, PNG, WebP, AVIF ๋“ฑ), ํ’ˆ์งˆ ์กฐ์ •์„ ํ†ตํ•œ ์†์‹ค ์••์ถ• ๋“ฑ์„ ํฌํ•จํ•˜์ฃ .

ํ™”๋ฉด ํฌ๊ธฐ์™€ ํ•ด์ƒ๋„์— ๋งž๊ฒŒ ์ตœ์ ํ™”๋œ ์ด๋ฏธ์ง€๋„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๊ณ , ์ฐจ์„ธ๋Œ€ ์ด๋ฏธ์ง€ ํฌ๋งท(WebP, AVFI)์„ ์ง€์›ํ•˜๋Š” ๋ธŒ๋ผ์šฐ์ €์—๋Š” ๋” ํšจ์œจ์ ์ธ ์••์ถ•๋ฅ ์„ ์ œ๊ณตํ•˜๋Š” ํ˜•์‹์œผ๋กœ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ •ํ•  ์ˆ˜๋„ ์žˆ์–ด์š”.

์ด๋ฏธ์ง€ CDN์ด๋‚˜ ์ž๋™ํ™”๋œ ์ด๋ฏธ์ง€ ์ตœ์ ํ™” ๋„๊ตฌ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์„œ๋ฒ„ ์ธก์—์„œ ๋™์ ์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ตœ์ ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

์••์ถ• ๋ฐ ์‚ฌ์ด์ฆˆ ์กฐ์ ˆ ์ „

ํ•ด๋‹น ์ด๋ฏธ์ง€๋Š” 11,375 × 8,992 ์‚ฌ์ด์ฆˆ ๋ฐ ์•ฝ 1MB ํฌ๊ธฐ๋ฅผ ๊ฐ€์ง„ JPG ์ด๋ฏธ์ง€์ž…๋‹ˆ๋‹ค.
์„œ๋น„์Šค์—์„œ 400x400 ์‚ฌ์ด์ฆˆ๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด์„œ ์ง์ ‘ width์™€ height๋ฅผ ์„ค์ •ํ•ด ์คฌ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณผ๊ฒŒ์š”.

<img src={largeImage} alt="image" width={400} height={400} />

์••์ถ• ๋ฐ ์‚ฌ์ด์ฆˆ ์กฐ์ ˆ ์ „

๋„คํŠธ์›Œํฌ ํƒญ์„ ํ™•์ธํ•ด๋ณด๋ฉด ์‹ค์ œ๋กœ ์•ฝ 1MB ์ •๋„์˜ ์ด๋ฏธ์ง€ ๋ฆฌ์†Œ์Šค๋ฅผ ์š”์ฒญํ•œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์••์ถ• ๋ฐ ์‚ฌ์ด์ฆˆ ์กฐ์ ˆ ํ›„

์••์ถ• ๋ฐ ์‚ฌ์ด์ฆˆ ์กฐ์ ˆ ํ›„

์ตœ๋Œ€ ์•ฝ 98%๊นŒ์ง€ ํฌ๊ธฐ๊ฐ€ ์ค„์–ด๋“  ๊ฒƒ์ด ๋ณด์ด์‹œ๋‚˜์š”?
๋„คํŠธ์›Œํฌํƒญ ๊ธฐ์ค€ ์ด๋ฆ„ ์ˆœ์„œ๋Œ€๋กœ ์›๋ณธ JPG > ์••์ถ• ๋ฐ ํฌ๊ธฐ ์กฐ์ ˆ JPG > ์••์ถ• ๋ฐ ํฌ๊ธฐ ์กฐ์ ˆ webp > ์••์ถ• ๋ฐ ํฌ๊ธฐ ์กฐ์ ˆ avif ์ˆœ์„œ์ž…๋‹ˆ๋‹ค.
์‹ค์ œ ํ•„์š”ํ•œ ํฌ๊ธฐ(400x400)๋งŒํผ ํฌ๊ธฐ๋ฅผ ์กฐ์ •ํ•˜๊ณ  ์••์ถ•์„ ์ง„ํ–‰ํ–ˆ์„ ๋ฟ์ด์—์š”.

์ฃผ์˜ํ•  ์ ์€ ๋ธŒ๋ผ์šฐ์ € ์ง€์›์— ๋”ฐ๋ผ์„œ ์ตœ์‹  ํฌ๋งท(AVIF, WebP)์„ ์šฐ์„ ์ ์œผ๋กœ ์ œ๊ณตํ•˜๊ณ  ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ์ด ์™ธ ํฌ๋งท์„ ํด๋ฐฑ ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.

<picture>
	{/* AVIF ์ง€์› ๋ธŒ๋ผ์šฐ์ €์šฉ */}
	<source srcSet={largeImageAvif} type="image/avif" />

	{/* WebP ์ง€์› ๋ธŒ๋ผ์šฐ์ €์šฉ */}
	<source srcSet={largeImageWebp} type="image/webp" />

	{/* ๋‘˜ ๋‹ค ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋ธŒ๋ผ์šฐ์ €์šฉ ํด๋ฐฑ */}
	<img src={largeImage2} alt="์ตœ์ ํ™” ์ด๋ฏธ์ง€" width={400} height={400} />
</picture>

์ด๋Ÿฐ ์‹์œผ๋กœ ๋ง์ด์ฃ .
๊ทธ๋Ÿผ ๋งŒ์•ฝ AVIF๋ฅผ ์ง€์›ํ•˜๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” AVIF ์ด๋ฏธ์ง€๋งŒ ์š”์ฒญํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ €์— ๋ณด์ด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

์••์ถ• ๋ฐ ์‚ฌ์ด์ฆˆ ์กฐ์ ˆ์„ ์ง„ํ–‰ํ•จ์œผ๋กœ์จ ๋Œ€์—ญํญ ์‚ฌ์šฉ๋Ÿ‰ ๊ฐ์†Œ ๋ฐ ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ๊ฐ„์„ ์ค„์ผ ์ˆ˜ ์žˆ๋Š” ์ด์ ์„ ๊ธฐ๋Œ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฏธ์ง€ ํฌ๊ธฐ ์กฐ์ ˆ ๋ฐ ์••์ถ• ์‚ฌ์ดํŠธ๋Š” ์—ฌ๋Ÿฌ ์„œ๋น„์Šค๊ฐ€ ์กด์žฌํ•ด์š”.
์ €๋Š” ์•„๋ž˜ ์„œ๋น„์Šค๋ฅผ ์ฃผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด์š”.
https://squoosh.app/

 

Squoosh

Simple Open your image, inspect the differences, then save instantly. Feeling adventurous? Adjust the settings for even smaller files.

squoosh.app

๐ŸŽฌ ๋™์˜์ƒ ํŒŒ์ผ ์ตœ์ ํ™”

๋™์˜์ƒ ๋˜ํ•œ  ํŒŒ์ผ ํฌ๊ธฐ๋ฅผ ์••์ถ•ํ•˜๊ณ  ํšจ์œจ์ ์ธ ์ฝ”๋ฑ(WebM)์„ ์‚ฌ์šฉํ•ด์„œ ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ์„ ๊ณ ๋ คํ•œ ์†Œ์Šค ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ง€์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ์ด๋ฏธ์ง€ ์ตœ์ ํ™” ๋ฐฉ์‹๊ณผ ๋™์ผํ•˜๊ฒŒ ํ™œ์šฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ ํ•„์š”ํ•œ ๊ธธ์ด๋งŒ ์‚ฌ์šฉํ•˜๊ณ , ํšจ์œจ์ ์ธ ์ฝ”๋ฑ์œผ๋กœ ๋ณ€ํ™˜, ์••์ถ•์„ ์ง„ํ–‰ํ•˜๋Š” ๋ฐฉ์‹์ด์ฃ .

์ฐจ์ด ์˜ˆ์‹œ ์ด๋ฏธ์ง€ ๋ฐ ์ฝ”๋“œ๋Š” ์œ„ ์ด๋ฏธ์ง€  ์••์ถ• ๋ฐ ์‚ฌ์ด์ฆˆ ์กฐ์ ˆ ์„น์…˜๊ณผ ๋™์ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์˜ˆ์‹œ๋ฅผ ์ฒจ๋ถ€ํ•˜์ง€๋Š” ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ฐ„๋‹จํ•˜๊ฒŒ๋งŒ ๋ณด์—ฌ๋“œ๋ฆฌ์ž๋ฉด,

<video controls preload="none" poster="thumbnail.jpg" width="640" height="360">
  <!-- WebM์ด ์ง€์›๋˜๋ฉด ๋จผ์ € ์‚ฌ์šฉ (๋” ํšจ์œจ์ ์ธ ์ฝ”๋ฑ) -->
  <source src="video.webm" type="video/webm">
  <!-- ๋Œ€์ฒด ํฌ๋งท์œผ๋กœ MP4 ์ œ๊ณต -->
  <source src="video.mp4" type="video/mp4">
</video>

๋™์˜์ƒ๋„ source ํƒœ๊ทธ๋ฅผ ์ง€์ •ํ•˜์—ฌ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์–ด์š”.

๋™์˜์ƒ ์••์ถ• ์„œ๋น„์Šค๋Š” media.io ์„œ๋น„์Šค๋ฅผ ์ž์ฃผ ์ด์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์••์ถ•
https://www.media.io/apps/compressor/

 

Online Video, Audio and Image Compressor - Reduce Large Files Size Online

 

www.media.io

์ฝ”๋ฑ ๋ณ€ํ™˜

https://www.media.io/apps/converter/

 

Online Video, Audio, and Image Converter with Batch Processing | Media.io Converter

 

www.media.io

๐Ÿ”ค ํฐํŠธ ์ตœ์ ํ™”

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


FOIT(Flash of Invisible Text)
ํฐํŠธ๋ฅผ ๋‹ค์šด๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ํ…์ŠคํŠธ๊ฐ€ ๋…ธ์ถœ๋˜์ง€ ์•Š๋Š” ํ˜„์ƒ

FOUT(Flash of Unstyled Text)
ํฐํŠธ๊ฐ€ ๋‹ค์šด๋กœ๋“œ๋˜๊ธฐ ์ „ ๊ธฐ๋ณธ ํฐํŠธ๋ฅผ ๋…ธ์ถœํ•˜๊ณ  ๋‹ค์šด๋กœ๋“œ ํ›„์— ํ•ด๋‹น ํฐํŠธ๋กœ ๊ต์ฒด๋˜๋Š” ํ˜„์ƒ


ํฐํŠธ ์ตœ์ ํ™” ๊ธฐ๋ฒ•์—๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜์—์„œ ์†Œ๊ฐœํ•ด๋“œ๋ฆฌ๋Š” ๊ธฐ๋ฒ•๋“ค์„ ์ƒํ™ฉ์— ๋งž๊ฒŒ ์ ์šฉํ•˜์—ฌ ์ตœ์ ํ™”๋ฅผ ์ง„ํ–‰ํ•ด ๋ณด์„ธ์š”!

font-display ์†์„ฑ ํ™œ์šฉ

font-display ์†์„ฑ์€ ํฐํŠธ ๋กœ๋”ฉ ์ค‘ ํ…์ŠคํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ํ‘œ์‹œํ• ์ง€ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.

@font-face {
  font-family: 'MyWebFont';
  src: url('myfont.woff2') format('woff2'),
       url('myfont.woff') format('woff');
  font-display: swap; /* ํ•ต์‹ฌ ์†์„ฑ */
  font-weight: normal;
  font-style: normal;
}
  • swap: ํฐํŠธ ๋กœ๋“œ ์ „๊นŒ์ง€ ์‹œ์Šคํ…œ ํฐํŠธ๋กœ ํ‘œ์‹œ (FOUT, ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋จ)
  • block: ํฐํŠธ ๋กœ๋“œ ๋  ๋•Œ๊นŒ์ง€ ํ…์ŠคํŠธ ์ˆจ๊น€, 3์ดˆ ์ฐจ๋‹จ ๊ธฐ๊ฐ„ ํ›„ ์‹œ์Šคํ…œ ํฐํŠธ ํ‘œ์‹œ (FOIT)
  • fallback: ๋งค์šฐ ์งง์€ FOIT ํ›„ ์‹œ์Šคํ…œ ํฐํŠธ ํ‘œ์‹œ, 3์ดˆ ๋‚ด ๋กœ๋“œ๋˜๋ฉด ์›นํฐํŠธ๋กœ ์ „ํ™˜ (FOIT)
  • optional: fallback๊ณผ ์œ ์‚ฌ. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋„คํŠธ์›Œํฌ ์ƒํƒœ์— ๋”ฐ๋ผ ๊ฒฐ์ •, ๋ณดํ†ต ํ•œ ๋ฒˆ๋งŒ ์‹œ๋„ํ•˜๊ณ  ์บ์‹œ (FOIT)
  • auto: ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ณธ๊ฐ’

ํฐํŠธ ํ”„๋ฆฌ๋กœ๋”ฉ

์ค‘์š”ํ•œ ํฐํŠธ๋Š” ํ”„๋ฆฌ๋กœ๋”ฉํ•˜์—ฌ ํŽ˜์ด์ง€ ๋กœ๋“œ ์ดˆ๊ธฐ์— ์š”์ฒญํ•  ์ˆ˜ ์žˆ์–ด์š”.

<link rel="preload" href="fonts/myfont.woff2" as="font" type="font/woff2" crossorigin>

"๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ ์ด ๋ฆฌ์†Œ์Šค๊ฐ€ ์ค‘์š”ํ•˜๋‹ˆ ๋จผ์ € ๊ฐ€์ ธ์™€!"๋ผ๊ณ  ์•Œ๋ ค์ฃผ๋Š” ๊ฒƒ์ด์ฃ .
ํ•ต์‹ฌ ํ…์ŠคํŠธ์— ์‚ฌ์šฉ๋˜๋Š” ํฐํŠธ์—๋งŒ ์‚ฌ์šฉํ•˜๊ณ , ๋‚จ์šฉํ•˜๋ฉด ์˜คํžˆ๋ ค ์„ฑ๋Šฅ์ด ์ €ํ•˜๋ฉ๋‹ˆ๋‹ค.

 

ํšจ์œจ์ ์ธ ํฐํŠธ ํฌ๋งท ์‚ฌ์šฉ

ํฐํŠธ์—๋„ ์—ฌ๋Ÿฌ ํฌ๋งท์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

  • WOFF2: ์ตœ์‹  ํฌ๋งท, ์ตœ๊ณ ์˜ ์••์ถ•๋ฅ  (์›๋ณธ ๋Œ€๋น„ 30% ์ด์ƒ ๊ฐ์†Œ), ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์›
  • WOFF: ๋Œ€๋ถ€๋ถ„์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์›, ์ ์ ˆํ•œ ์••์ถ•๋ฅ 
  • TTF/OTF: ๋ ˆ๊ฑฐ์‹œ ์ง€์›์šฉ, ์••์ถ•๋ฅ  ๋‚ฎ์Œ
  • EOT: ์˜ค๋ž˜๋œ IE ์ง€์›์šฉ, ๊ฑฐ์˜ ์‚ฌ์šฉ ์•ˆ ํ•จ

WOFF2 -> WOFF -> TTF/OTF -> EOT ์ˆœ์œผ๋กœ ํฌ๊ธฐ๊ฐ€ ์ปค์ง‘๋‹ˆ๋‹ค.

 

์ตœ์‹  ํฌ๋งท์€ ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ €์—์„œ๋งŒ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ง€์›ํ•˜๋Š” ๋ธŒ๋ผ์šฐ์ €์— ๋งž๋Š” ํฌ๋งท์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์–ด์š”.

@font-face {
  font-family: 'MyWebFont';
  src: url('myfont.woff2') format('woff2'),
       url('myfont.woff') format('woff'),
       url('myfont.ttf') format('truetype');
  font-display: swap;
}

 

ํฐํŠธ ํฌ๋งท ๋ณ€ํ™˜ ์‚ฌ์ดํŠธ์—์„œ ์‰ฝ๊ฒŒ ํ•˜๋‚˜์˜ ํฐํŠธ๋กœ ์—ฌ๋Ÿฌ ํฌ๋งท์— ๋Œ€ํ•œ ํฐํŠธ ํŒŒ์ผ์„ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

์„œ๋ธŒ์…‹ ํฐํŠธ ์‚ฌ์šฉ

์„œ๋ธŒ์…‹ ํฐํŠธ(Subset Font)๋Š” ํ•„์š”ํ•œ ๋ฌธ์ž๋งŒ ๋‹ค์šด๋กœ๋“œํ•ด์„œ ํฐํŠธ ํฌ๊ธฐ๋ฅผ ์ตœ์†Œํ™” ํ•œ ํฐํŠธ์ž…๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ํ•œ๊ธ€ ๊ฐ™์€ ๊ฒฝ์šฐ ๊ถณ, ๋›Ÿ, ์Œป ๋“ฑ๋“ฑ ์ด๋Ÿฐ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๋ฌธ์ž๋“ค์€ ๊ณผ๊ฐํžˆ ๋ฒ„๋ฆฌ๊ณ  ์ •๋ง ์‚ฌ์šฉํ•  ๊ธ€์ž๋งŒ ํฐํŠธ์— ํฌํ•จ์‹œํ‚ค๋Š” ๊ฒƒ์ด์ฃ .

์„œ๋ธŒ์…‹ ์›น ํฐํŠธ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์‚ฌ์ดํŠธ์—์„œ ๋ณ€ํ™˜์„ ํ•ด์„œ ํฌ๊ธฐ๊ฐ€ ์ž‘์€ ์ƒˆ๋กœ์šด ํฐํŠธ ํŒŒ์ผ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํฐํŠธ๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ  ๋ณ€ํ™˜๋งŒ ํ•ด์ฃผ๋ฉด ๊ฐ„๋‹จํ•˜๊ฒŒ ๋ณ€ํ™˜์ด ๋˜๋Š”๋ฐ์š”.

ํ•ด๋‹น ์‚ฌ์ดํŠธ๋Š” ํ•œ๊ธ€์„ ์ง€์›ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ง์ ‘ ์‚ฌ์šฉํ•  ๋ฌธ์ž๋“ค์„ ๋„ฃ์–ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.


๊ตฌ๊ธ€์— "ํ•œ๊ธ€ ์„œ๋ธŒ์…‹ ๋ฆฌ์ŠคํŠธ"๋ผ๊ณ  ๊ฒ€์ƒ‰ํ•˜์‹œ๋ฉด ์‰ฝ๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

unicode-range ์†์„ฑ ์‚ฌ์šฉ

์„œ๋ธŒ์…‹ ํฐํŠธ์™€ ๊ฐ™์ด ์กฐํ•ฉํ•ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ์ตœ์ ํ™”๋ฅผ ๊ทน๋Œ€ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

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

/* ๋ผํ‹ด ๋ฌธ์ž์šฉ ํฐํŠธ ์„ ์–ธ */
@font-face {
  font-family: 'MyFont';
  src: url('myfont.woff2') format('woff2');
  unicode-range: U+0000-00FF; /* ๋ผํ‹ด ๋ฌธ์ž ๋ฒ”์œ„ */
  font-display: swap;
}

/* ํ•œ๊ธ€์šฉ ํฐํŠธ ์„ ์–ธ */
@font-face {
  font-family: 'MyFont';
  src: url('myfont.woff2') format('woff2');
  unicode-range: U+AC00-D7AF; /* ํ•œ๊ธ€ ๋ฒ”์œ„ */
  font-display: swap;
}

์›๋ณธ ํฐํŠธ ํŒŒ์ผ์€ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ณ , ํŠน์ • ํฐํŠธ ์„ ์–ธ์ด ์ ์šฉ๋  ์œ ๋‹ˆ์ฝ”๋“œ ๋ฒ”์œ„๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํŽ˜์ด์ง€์— ํ•ด๋‹น ๋ฒ”์œ„์˜ ๋ฌธ์ž๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ํฐํŠธ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๊ฒŒ ๋˜์ฃ .

 

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํฐํŠธ ํŒŒ์ผ ํฌ๊ธฐ๋„ ์ž‘์•„์ง€๊ณ , ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ๋‹ค์šด๋กœ๋“œ๋˜๋Š” ์ตœ์ ์˜ ํฐํŠธ ์ „๋žต์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐ŸŒ CDN ์‚ฌ์šฉ

์„œ๋ฒ„์™€ ์‚ฌ์šฉ์ž ๊ฐ„์˜ ๋ฌผ๋ฆฌ์  ๊ฑฐ๋ฆฌ๊ฐ€ ๋ฉ€์ˆ˜๋ก ๋ฐ์ดํ„ฐ ์ „์†ก ์‹œ๊ฐ„์ด ๊ธธ์–ด์ง€๊ณ , ๋‹จ์ผ ์„œ๋ฒ„๋Š” ํŠธ๋ž˜ํ”ฝ ์ฆ๊ฐ€ ์‹œ ๋ณ‘๋ชฉ ํ˜„์ƒ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด์š”.

 

CDN(Content Delivery Network)์€ ์ „ ์„ธ๊ณ„ ์—ฌ๋Ÿฌ ์„œ๋ฒ„์— ์ฝ˜ํ…์ธ ๋ฅผ ๋ถ„์‚ฐ ์ €์žฅํ•˜์—ฌ ์‚ฌ์šฉ์ž์™€ ๊ฐ€๊นŒ์šด ์œ„์น˜์—์„œ ์ปจํ…์ธ ๋ฅผ ์ œ๊ณตํ•˜๋Š” ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.

์ •์  ์ž์‚ฐ(์ด๋ฏธ์ง€, CSS, JavaScript, ๋น„๋””์˜ค ๋“ฑ)๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋™์  ์ฝ˜ํ…์ธ ๊นŒ์ง€ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ณ , ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ์œผ๋กœ ์ž๋™ ํŒŒ์ผ ์••์ถ•, ์ด๋ฏธ์ง€ ์ตœ์ ํ™”์™€ ๊ฐ™์€ ์ถ”๊ฐ€์ ์ธ ๊ฒƒ๋“ค๊นŒ์ง€ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”.

CDN ์„œ๋น„์Šค ์ค‘์—๋Š” Cloudinary, Cloudflare, AWS CloudFront, Vercel, Netlify ๋“ฑ์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.
CDN์˜ ์ด๋ฏธ์ง€๋ฅผ ํ™œ์šฉํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์„ ๋•Œ ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”.

function OptimizedProductImage({ productId, alt }) {
  const baseUrl = "https://res.cloudinary.com/your-cloud-name/image/upload"; // ์—…๋กœ๋“œ ๋œ uri
  const transformations = "f_auto,q_auto,w_800,c_fill"; // ์ด๋ฏธ์ง€ ํฌ๊ธฐ ๋™์ ์œผ๋กœ ๋ฐ›๊ธฐ
  const imagePath = `products/${productId}.jpg`;
  
  return (
    <img 
      src={`${baseUrl}/${transformations}/${imagePath}`} 
      alt={alt}
      loading="lazy"
    />
  );
}


CDN์„ ํ™œ์šฉํ–ˆ์„ ๋•Œ ์ด์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์•„์š”.

  • ๋กœ๋”ฉ ์†๋„ ํ–ฅ์ƒ: ์‚ฌ์šฉ์ž์™€ ๊ฐ€๊นŒ์šด ์„œ๋ฒ„์—์„œ ์ฝ˜ํ…์ธ  ์ œ๊ณต
  • ์„œ๋ฒ„ ๋ถ€ํ•˜ ๋ถ„์‚ฐ: ์›๋ณธ ์„œ๋ฒ„์˜ ๋ถ€๋‹ด ๊ฐ์†Œ
  • ๊ฐ€์šฉ์„ฑ ํ–ฅ์ƒ: ์ผ๋ถ€ ์„œ๋ฒ„์— ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ์„œ๋น„์Šค ์œ ์ง€
  • ๋ณด์•ˆ ๊ฐ•ํ™”: DDos ๋ฐฉ์–ด, WAF ๋“ฑ ๋ณด์•ˆ ๊ธฐ๋Šฅ ์ œ๊ณต

๐Ÿ“œ ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ ๋ฆฌ์ŠคํŠธ์— ๊ฐ€์ƒํ™” ์ ์šฉ

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

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

๊ฐ€์ƒํ™”๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๊ฐ€์ƒํ™”๋ฅผ ์ง€์›ํ•˜๋Š” react-window, react-virtualized, react-virtuoso ๋“ฑ ์ž˜ ๋งŒ๋“ค์–ด์ง„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค๋„ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.
์ €๋Š” ์˜ˆ์‹œ์—์„œ react-virtuoso๋ฅผ ์‚ฌ์šฉํ•  ๊ฑฐ์˜ˆ์š”.

 

๊ฐ€์ƒํ™” ์ ์šฉ ์ „

์˜ˆ์‹œ๋กœ, 1๋งŒ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋ณด์—ฌ์ค˜์•ผ ํ•˜๋Š” ์ƒํ™ฉ์„ ๊ฐ€์ •ํ•ด ๋ณผ๊ฒŒ์š”.

function Test() {
	// ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ (์˜ˆ: 10,000๊ฐœ ํ•ญ๋ชฉ)
	const data = Array.from({ length: 10000 }).map((_, index) => ({
		id: index,
		text: `ํ•ญ๋ชฉ ${index + 1}`,
	}));

	return (
		<div style={{ height: '400px', width: '100%', overflowY: 'auto' }}>
			{data.map((item) => (
				<div
					key={item.id}
					style={{ padding: '12px', borderBottom: '1px solid #eee' }}
				>
					{item.text}
				</div>
			))}
		</div>
	);
}

export default Test;

๊ฐ€์ƒํ™” ์ ์šฉ์ „

ํ™”๋ฉด์ด ๋ Œ๋”๋ง ๋  ๋•Œ 1๋งŒ ๊ฐœ์˜ DOM ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ Œ๋”๋ง ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ฆ‰, ํ˜„์žฌ ๋ณด์ด์ง€ ์•Š๋Š” ์˜์—ญ์˜ ๋ฐ์ดํ„ฐ๊นŒ์ง€ DOM ๋…ธ๋“œ์— ์ถ”๊ฐ€๋œ ์ƒํ™ฉ์ด์ฃ .

์ดˆ๊ธฐ์— ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๋ถˆ๋Ÿฌ์™€์„œ DOM ๋…ธ๋“œ์— ์ถ”๊ฐ€ํ•˜์—ฌ ๋ Œ๋”๋ง ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ดˆ๊ธฐ ๋กœ๋”ฉ ์†๋„ ๋˜ํ•œ ๋‹น์—ฐํžˆ ๋А๋ฆด ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ฐ€์ƒํ™” ์ ์šฉ ํ›„

import { Virtuoso } from 'react-virtuoso';

function Test2() {
	// ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ (์˜ˆ: 10,000๊ฐœ ํ•ญ๋ชฉ)
	const data = Array.from({ length: 10000 }).map((_, index) => ({
		id: index,
		text: `ํ•ญ๋ชฉ ${index + 1}`,
	}));

	return (
		<Virtuoso
			style={{ height: '400px', width: '100%' }}
			totalCount={data.length}
			itemContent={(index) => (
				<div style={{ padding: '12px', borderBottom: '1px solid #eee' }}>
					{data[index].text}
				</div>
			)}
		/>
	);
}

export default Test2;

 

์‹ค์ œ ํ™”๋ฉด์— ๋ณด์ด๋Š” ์š”์†Œ ๋ฐ ์ฃผ๋ณ€ ์ผ๋ถ€๋งŒ DOM ๋…ธ๋“œ์— ์ถ”๊ฐ€๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

 

์ด์ ์„ ์ •๋ฆฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ์‹ค์ œ DOM ๋…ธ๋“œ ์ˆ˜๋ฅผ ํฌ๊ฒŒ ์ค„์—ฌ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ตœ์ ํ™”
  • ์ดˆ๊ธฐ ๋ Œ๋”๋ง ๋ฐ ์Šคํฌ๋กค ์„ฑ๋Šฅ ํ–ฅ์ƒ (๋ Œ๋”๋ง ์—ฐ์‚ฐ ์ตœ์†Œํ™”)
  • ์ˆ˜์ฒœ, ์ˆ˜๋งŒ ๊ฐœ์˜ ํ•ญ๋ชฉ๋„ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์Šคํฌ๋กค ๊ฐ€๋Šฅ

โšก ์• ๋‹ˆ๋ฉ”์ด์…˜์—” requestAnimationFrame(rAF)๋ฅผ ํ™œ์šฉํ•ด๋ณด๊ธฐ

requestAnimationFrame(rAF)์€ ๋ธŒ๋ผ์šฐ์ €์˜ ๋ฆฌํŽ˜์ธํŠธ ์ฃผ๊ธฐ์— ๋งž์ถฐ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด๋‚˜ ์‹œ๊ฐ์  ์—…๋ฐ์ดํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” Javascript API์ž…๋‹ˆ๋‹ค.

๋ธŒ๋ผ์šฐ์ €์˜ ๋ Œ๋”๋ง ์—”์ง„๊ณผ ๋™๊ธฐํ™”๋˜์–ด ์ž‘๋™ํ•˜๋ฏ€๋กœ, ์ผ๋ฐ˜์ ์œผ๋กœ ์ดˆ๋‹น 60 ํ”„๋ ˆ์ž„(60 fps, ์•ฝ 16.7ms)์˜ ์†๋„๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

 

 

์œ„ ์˜ˆ์ œ๋Š” setInterval๊ณผ rAF๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ width๋ฅผ ์ฆ๊ฐ€์‹œํ‚ค๋Š” ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.
"์‹œ์ž‘" ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ๋ณด๋ฉด rAF๋Š” ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ฆ๊ฐ€๋˜๋Š” ๋ฐ˜๋ฉด, setInterval์€ ์ค‘๊ฐ„์ค‘๊ฐ„ ๋Š๊ธฐ๋Š” ๋А๋‚Œ์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

 

๊ฐ„๋‹จํ•˜๊ฒŒ ์ฐจ์ด์ ์„ ์•Œ์•„๋ณผ๊ฒŒ์š”.

 

ํŠน์„ฑ setTimeout/setInterval requestAnimationFrame
ํƒ€์ด๋ฐ ์ง€์ •๋œ ์‹œ๊ฐ„ ํ›„ ์‹คํ–‰ ๋ธŒ๋ผ์šฐ์ € ๋ฆฌํŽ˜์ธํŠธ ์ง์ „์— ์‹คํ–‰
ํ”„๋ ˆ์ž„ ๋ ˆ์ดํŠธ ๊ณ ์ • ๊ฐ„๊ฒฉ (๋ถˆ๊ทœ์น™ํ•  ์ˆ˜ ์žˆ์Œ) ํ™”๋ฉด ์ฃผ์‚ฌ์œจ์— ์ตœ์ ํ™” (๋ณดํ†ต 60fps)
๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋™์ž‘ ๊ณ„์† ์‹คํ–‰ (์ œํ•œ๋  ์ˆ˜ ์žˆ์Œ) ๋น„ํ™œ์„ฑ ํƒญ์—์„œ ์ผ์‹œ ์ค‘์ง€ (๋ฆฌ์†Œ์Šค ์ ˆ์•ฝ)
์ •ํ™•์„ฑ ๋ธŒ๋ผ์šฐ์ € ์ œํ•œ ์žˆ์Œ (์ตœ์†Œ 4ms) ๋ Œ๋”๋ง ํŒŒ์ดํ”„๋ผ์ธ๊ณผ ์ •ํ™•ํžˆ ๋™๊ธฐํ™”

 

rAF๋Š” ํŠนํžˆ ์• ๋‹ˆ๋ฉ”์ด์…˜, ์Šคํฌ๋กค ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ, ์บ”๋ฒ„์Šค ๊ทธ๋ฆฌ๊ธฐ, ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™” ๋“ฑ ๋ถ€๋“œ๋Ÿฌ์šด ์‹œ๊ฐ์  ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•œ ์ƒํ™ฉ์—์„œ ์ตœ๊ณ ์˜ ์„ฑ๋Šฅ์„ ๋ฐœํœ˜ํ•ด์š”.

๐Ÿ“ฆ React ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ…๊ณผ ์ง€์—ฐ ๋กœ๋”ฉ (Lazy Loading)

์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ž‘์€ ์ฒญํฌ(chunk)๋กœ ๋ถ„ํ• ํ•˜๊ณ , ํ•„์š”ํ•œ ์‹œ์ ์— ํ•ด๋‹น ์ฒญํฌ๋งŒ ๋™์ ์œผ๋กœ ๋กœ๋“œํ•˜๋Š” ์ตœ์ ํ™” ๊ธฐ๋ฒ•์ž…๋‹ˆ๋‹ค.

์ดˆ๊ธฐ ๋กœ๋”ฉ ์‹œ ํ•„์š”ํ•œ ์ฝ”๋“œ๋งŒ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ , ๋‚˜๋จธ์ง€๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ํ•ด๋‹น ๊ธฐ๋Šฅ์ด๋‚˜ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•  ๋•Œ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ์–ด์š”.

 

React๋Š” React.lazy()์™€ Suspense ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ…๊ณผ ์ง€์—ฐ๋กœ๋”ฉ์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•ฉ๋‹ˆ๋‹ค!

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Navbar from './components/Navbar';

// ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ ์ง€์—ฐ ๋กœ๋”ฉ
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const UserProfile = lazy(() => import('./pages/UserProfile'));

function App() {
  return (
    <Router>
      <Navbar />
      
      <Suspense fallback={<div className="page-loader">ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์ค‘...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/profile/:userId" element={<UserProfile />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

export default App;

์œ„์™€ ๊ฐ™์ด ์†์‰ฝ๊ฒŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŽ˜์ด์ง€๋ฅผ ๊ธฐ์ค€์œผ๋กœ lazy loading์„ ์ ์šฉํ–ˆ์–ด์š”.

 

๋ฆฌ์•กํŠธ ํ”„๋กœ์ ํŠธ๋ฅผ ๋นŒ๋“œํ•˜๊ณ  ์ฒซ ํŽ˜์ด์ง€์— ์ง„์ž…ํ–ˆ์„ ๋•Œ์˜ ์˜ˆ์‹œ๋ฅผ ํ•œ๋ฒˆ ๋ณผ๊นŒ์š”?

์ ์šฉ ์ „

์ ์šฉ ์ „ ๋นŒ๋“œ ๊ฒฐ๊ณผ
์ ์šฉ ์ „ ์ฒซ ํŽ˜์ด์ง€ ์ง„์ž…

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

 

์•ฑ ํฌ๊ธฐ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ๋‹จ์ผ ๋ฒˆ๋“ค์˜ ํฌ๊ธฐ๊ฐ€ ์ผœ์ ธ์„œ ์ดˆ๊ธฐ ๋กœ๋”ฉ ์‹œ๊ฐ„์ด ์ฆ๊ฐ€ํ•˜๊ณ , ๋ชจ๋ฐ”์ผ ํ™˜๊ฒฝ์ด๋‚˜ ๋А๋ฆฐ ๋„คํŠธ์›Œํฌ ํ™˜๊ฒฝ์—์„œ๋Š” ํŠนํžˆ ๋” ๋А๋ฆด ์ˆ˜๊ฐ€ ์žˆ์–ด์š”.

์ ์šฉ ํ›„

์ ์šฉ ํ›„ ๋นŒ๋“œ ๊ฒฐ๊ณผ
์ ์šฉ ํ›„ ์ฒซ ํŽ˜์ด์ง€ ์ง„์ž…

lazy loading์„ ์ ์šฉํ•œ ๊ฒฐ๊ณผ ๊ฐ ํŽ˜์ด์ง€, ์ปดํฌ๋„ŒํŠธ ๋ณ„๋กœ js๊ฐ€ ๋ถ„๋ฆฌ๋˜์—ˆ์–ด์š”!
์ฆ‰, ์ •๋ง ํ•„์š”ํ•œ jsํŒŒ์ผ๋งŒ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

 

์ด๋ ‡๊ฒŒ ํ–ˆ์„ ๋•Œ ์ด์ ๋“ค์€ ๋ญ˜๊นŒ์š”?

  • FCP(First Contentful Paint), TTI(Time to Interaction) ๊ฐœ์„ 
  • Core Web Vitals ์ ์ˆ˜ ํ–ฅ์ƒ์œผ๋กœ SEO์—๋„ ๐Ÿ‘
  • ํŠน์ • ๊ธฐ๋Šฅ๋งŒ ์—…๋ฐ์ดํŠธ ์‹œ ํ•ด๋‹น ์ฒญํฌ๋งŒ ๋‹ค์‹œ ๋‹ค์šด๋กœ๋“œ
  • ์‚ฌ์šฉ์ž๊ฐ€ ์‹ค์ œ๋กœ ํ•„์š”๋กœ ํ•˜๋Š” ์ฝ”๋“œ๋งŒ ๋‹ค์šด๋กœ๋“œํ•˜์—ฌ ๋Œ€์—ญํญ ์ ˆ์•ฝ

์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

 

์กฐ๊ธˆ ๋” ๊นŠ๊ฒŒ ๋“ค์–ด๊ฐ€๋ฉด ์กฐ๊ฑด๋ถ€ ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ…, ํ”„๋ฆฌ๋กœ๋”ฉ ํ™œ์šฉ, ์ฒญํฌ ํฌ๊ธฐ ์ตœ์ ํ™”๊นŒ์ง€ ๊ณ ๋ คํ•ด ๋ณผ ์ˆ˜ ์žˆ์–ด์š”.

๐Ÿ“• ๋งˆ์น˜๋ฉฐ

ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ๋‹น์žฅ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ์ ํ™” ๋ฐฉ๋ฒ•๋“ค์„ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

์ œ๊ฐ€ ์†Œ๊ฐœํ•œ ์ตœ์ ํ™” ๊ธฐ๋ฒ•๋“ค์€ ์‹œ์ž‘์— ๋ถˆ๊ณผํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

์—ฐ๊ตฌ์— ๋”ฐ๋ฅด๋ฉด ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์‹œ๊ฐ„์ด 3์ดˆ๋ฅผ ๋„˜์–ด๊ฐˆ ๋•Œ๋งˆ๋‹ค ์ดํƒˆ๋ฅ ์ด 32% ์ฆ๊ฐ€ํ•œ๋‹ค๊ณ  ํ•˜๋„ค์š”.๐Ÿฅถ

 

์—ฌ๋Ÿฌ๋ถ„์˜ ์„œ๋น„์Šค์—์„œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์‹ค์งˆ์ ์œผ๋กœ ๊ฐœ์„ ํ•˜๊ณ  ๋น„์ฆˆ๋‹ˆ์Šค ๋ชฉํ‘œ๊นŒ์ง€ ๋‹ฌ์„ฑํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ์–ด์š”.

 

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

 

๋ฐ˜์‘ํ˜•
profile

๐Ÿ’ป์šฉ๋‡ฝ ๊ฐœ๋ฐœ ๋…ธํŠธ๐Ÿ’ป

@์šฉ๋‡ฝ

ํฌ์ŠคํŒ…์ด ์ข‹์•˜๋‹ค๋ฉด "์ข‹์•„์š”โค๏ธ" ๋˜๋Š” "๊ตฌ๋…๐Ÿ‘๐Ÿป" ํ•ด์ฃผ์„ธ์š”!