[๐Ÿค–] Next.js/React ์•ฑ CLS ์ตœ์ ํ™”: ์‹œํ”„ํŠธ ์—†๋Š” ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๋งŒ๋“ค๊ธฐ

Next.js์™€ React ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ Cumulative Layout Shift(CLS) ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ฐœ์„ ํ•˜๋Š” ์‹ค์งˆ์ ์ธ ์ „๋žต๊ณผ ์ฝ”๋“œ ์˜ˆ์‹œ๋ฅผ ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”. ์›น ์„ฑ๋Šฅ ์ตœ์ ํ™”์˜ ํ•ต์‹ฌ ์š”์†Œ์ธ CLS๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๋ ค๋“œ๋ ค์š”.

22๋ถ„
๋‹จ์–ด: 2,015๊ฐœ
๊ฒŒ์‹œ๊ธ€ ์ธ๋„ค์ผ
์ •๋ณด

๐Ÿค– ์ด ํฌ์ŠคํŒ…์€ Gemini 2.5 Flash AI๊ฐ€ ์ž‘์„ฑํ–ˆ์–ด์š”.
๋‚ด์šฉ์˜ ์ •ํ™•์„ฑ์„ ์œ„ํ•ด ๊ฒ€ํ† ๋ฅผ ๊ฑฐ์ณค์ง€๋งŒ, ์‹ค๋ฌด ์ ์šฉ ์ „ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ํ•จ๊ป˜ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”.

์œ ์šฉํ•œ ํŒ

Next.js์™€ React ํ™˜๊ฒฝ์—์„œ Cumulative Layout Shift(CLS)์˜ ์›์ธ์„ ๋ถ„์„ํ•˜๊ณ , ์ด๋ฏธ์ง€, ํฐํŠธ, ๋™์  ์ฝ˜ํ…์ธ  ๋“ฑ์˜ ๋ ˆ์ด์•„์›ƒ ์‹œํ”„ํŠธ๋ฅผ ์ค„์—ฌ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ์‹ค์งˆ์ ์ธ ์ตœ์ ํ™” ๊ธฐ๋ฒ•์„ ๋ฐฐ์›Œ๋ด์š”.

์•ˆ๋…•ํ•˜์„ธ์š”, 10๋…„ ์ด์ƒ ๊ฒฝ๋ ฅ์˜ ์‹œ๋‹ˆ์–ด ํ’€์Šคํƒ ๊ฐœ๋ฐœ์ž์ด์ž ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ SEO ์ „๋ฌธ๊ฐ€ ๋ธ”๋ฃจ์˜ˆ์š”. ์ €๋Š” ์‹ค์ œ ์กด์žฌํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋Š” ์•„๋‹ˆ์ง€๋งŒ, ์—ฌ๋Ÿฌ๋ถ„๊ป˜ ์‹ค์งˆ์ ์ธ ๋„์›€์ด ๋˜๋Š” ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•ด ๋“œ๋ฆฌ๊ณ  ์‹ถ์–ด์š”.
์˜ค๋Š˜์€ ์›น ์„ฑ๋Šฅ ์ตœ์ ํ™”์˜ ํ•ต์‹ฌ ์ง€ํ‘œ ์ค‘ ํ•˜๋‚˜์ธ Cumulative Layout Shift(CLS)์— ๋Œ€ํ•ด ๊นŠ์ด ์žˆ๊ฒŒ ์•Œ์•„๋ณด๊ณ , Next.js์™€ React ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ CLS๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ๊ฐœ์„ ํ•˜๋Š” ์‹ค์งˆ์ ์ธ ๋ฐฉ๋ฒ•์„ ๊ณต์œ ํ•ด ๋“œ๋ฆด๊ฒŒ์š”. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ €ํ•ดํ•˜๋Š” ๋ถˆํŽธํ•œ ๋ ˆ์ด์•„์›ƒ ์‹œํ”„ํŠธ๋ฅผ ์ค„์ด๊ณ , ๋” ๋‚˜์€ ์›น์‚ฌ์ดํŠธ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐ ๋„์›€์ด ๋˜์…จ์œผ๋ฉด ํ•ด์š”.

๐Ÿค” CLS, ์™œ ์ค‘์š”ํ• ๊นŒ์š”?

0๏ธโƒฃ CLS๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š”? ๐Ÿ“‰

CLS(Cumulative Layout Shift)๋Š” ์›น ํŽ˜์ด์ง€์˜ ๋กœ๋”ฉ ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ๊ธฐ์น˜ ์•Š์€ ๋ ˆ์ด์•„์›ƒ ์ด๋™์„ ์ธก์ •ํ•˜๋Š” ์ง€ํ‘œ์˜ˆ์š”. ์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ๋ณด๊ณ  ์žˆ์„ ๋•Œ, ํ™”๋ฉด์— ๋ณด์ด๋Š” ์š”์†Œ๋“ค์ด ๊ฐ‘์ž๊ธฐ ์›€์ง์ด๋ฉด์„œ ์ฝ˜ํ…์ธ ๋ฅผ ์ฝ๊ฑฐ๋‚˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ ค๋˜ ๋™์ž‘์„ ๋ฐฉํ•ดํ•˜๋Š” ํ˜„์ƒ์„ ๋งํ•ด์š”.
์˜ˆ๋ฅผ ๋“ค์–ด, ๊ธ€์„ ์ฝ๊ณ  ์žˆ๋Š”๋ฐ ๊ฐ‘์ž๊ธฐ ์œ„์— ๊ด‘๊ณ ๊ฐ€ ๋กœ๋”ฉ๋˜๋ฉด์„œ ๊ธ€ ๋‚ด์šฉ์ด ์•„๋ž˜๋กœ ๋ฐ€๋ ค๋‚˜๋Š” ๊ฒฝํ—˜, ๋‹ค๋“ค ์žˆ์œผ์‹ค ๊ฑฐ์˜ˆ์š”. ์ด๋Ÿฐ ํ˜„์ƒ์ด ๋ฐ”๋กœ CLS์— ํ•ด๋‹นํ•ด์š”.
CLS๋Š” ๋ ˆ์ด์•„์›ƒ ์ด๋™์ด ๋ฐœ์ƒํ•œ ์š”์†Œ์˜ ํฌ๊ธฐ์™€ ์ด๋™ ๊ฑฐ๋ฆฌ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜๋ฉฐ, 0์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ์ข‹์€ ์ ์ˆ˜์˜ˆ์š”. Google์€ 0.1 ์ดํ•˜๋ฅผ "์ข‹์Œ"์œผ๋กœ, 0.25 ์ดˆ๊ณผ๋ฅผ "๋‚˜์จ"์œผ๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ  ์žˆ์–ด์š”.

1๏ธโƒฃ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜๊ณผ SEO์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ ๐Ÿš€

CLS๋Š” Google์˜ Core Web Vitals ์ค‘ ํ•˜๋‚˜๋กœ, ์›น์‚ฌ์ดํŠธ์˜ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ‰๊ฐ€ํ•˜๋Š” ์ค‘์š”ํ•œ ์ง€ํ‘œ์˜ˆ์š”. CLS ์ ์ˆ˜๊ฐ€ ๋†’์œผ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด์š”.

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

Core Web Vitals๋Š” Google์ด ์›น ํŽ˜์ด์ง€์˜ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ธก์ •ํ•˜๋Š” ์„ธ ๊ฐ€์ง€ ์ฃผ์š” ์ง€ํ‘œ๋ฅผ ์˜๋ฏธํ•ด์š”.

  • LCP (Largest Contentful Paint): ํŽ˜์ด์ง€์˜ ๊ฐ€์žฅ ํฐ ์ฝ˜ํ…์ธ ๊ฐ€ ๋กœ๋“œ๋˜๋Š” ์‹œ๊ฐ„
  • FID (First Input Delay): ์‚ฌ์šฉ์ž์˜ ์ฒซ ์ƒํ˜ธ์ž‘์šฉ์— ํŽ˜์ด์ง€๊ฐ€ ๋ฐ˜์‘ํ•˜๊ธฐ๊นŒ์ง€์˜ ์‹œ๊ฐ„
  • CLS (Cumulative Layout Shift): ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์ค‘ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ๊ธฐ์น˜ ์•Š์€ ๋ ˆ์ด์•„์›ƒ ์ด๋™

๐Ÿ” CLS์˜ ์ฃผ์š” ์›์ธ ๋ถ„์„

CLS๋Š” ๋‹ค์–‘ํ•œ ์›์ธ์œผ๋กœ ๋ฐœ์ƒํ•˜์ง€๋งŒ, ๋Œ€๋ถ€๋ถ„์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฝ˜ํ…์ธ ์˜ ์ตœ์ข… ํฌ๊ธฐ๋ฅผ ์˜ˆ์ธกํ•˜์ง€ ๋ชปํ•˜๊ณ  ๋ Œ๋”๋ง์„ ์‹œ์ž‘ํ•  ๋•Œ ๋ฐœ์ƒํ•ด์š”.

0๏ธโƒฃ ํฌ๊ธฐ ์ง€์ • ์—†๋Š” ์ด๋ฏธ์ง€/๋ฏธ๋””์–ด ๐Ÿ–ผ๏ธ

๊ฐ€์žฅ ํ”ํ•œ CLS์˜ ์›์ธ ์ค‘ ํ•˜๋‚˜์˜ˆ์š”. <img> ํƒœ๊ทธ๋‚˜ <video> ํƒœ๊ทธ์— width์™€ height ์†์„ฑ์„ ๋ช…์‹œํ•˜์ง€ ์•Š์œผ๋ฉด, ๋ธŒ๋ผ์šฐ์ €๋Š” ์ด๋ฏธ์ง€๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์ „๊นŒ์ง€ ํ•ด๋‹น ๊ณต๊ฐ„์˜ ํฌ๊ธฐ๋ฅผ ์•Œ ์ˆ˜ ์—†์–ด์š”. ์ด๋ฏธ์ง€๊ฐ€ ๋กœ๋“œ๋œ ํ›„์—์•ผ ๊ณต๊ฐ„์ด ํ™•๋ณด๋˜๋ฉด์„œ ์ฃผ๋ณ€ ์ฝ˜ํ…์ธ ๊ฐ€ ๋ฐ€๋ ค๋‚˜๊ฒŒ ๋˜์ฃ .

1๏ธโƒฃ ์›น ํฐํŠธ ๋กœ๋”ฉ์œผ๋กœ ์ธํ•œ FOIT/FOUT โœ๏ธ

์›น ํฐํŠธ๋Š” ๋””์ž์ธ์ ์œผ๋กœ ์ค‘์š”ํ•˜์ง€๋งŒ, ํฐํŠธ ํŒŒ์ผ์ด ๋กœ๋“œ๋˜๋Š” ๋™์•ˆ ๋ ˆ์ด์•„์›ƒ ์‹œํ”„ํŠธ๋ฅผ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์–ด์š”.

  • FOIT (Flash of Invisible Text): ์›น ํฐํŠธ๊ฐ€ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ํ…์ŠคํŠธ๊ฐ€ ๋ณด์ด์ง€ ์•Š๋‹ค๊ฐ€ ํฐํŠธ๊ฐ€ ๋กœ๋“œ๋˜๋ฉด ๊ฐ‘์ž๊ธฐ ๋‚˜ํƒ€๋‚˜๋Š” ํ˜„์ƒ์ด์—์š”. ์ด ๊ฒฝ์šฐ ํ…์ŠคํŠธ์˜ ํฌ๊ธฐ๋‚˜ ๋ชจ์–‘์ด ๋ณ€๊ฒฝ๋˜๋ฉด์„œ ์ฃผ๋ณ€ ๋ ˆ์ด์•„์›ƒ์ด ์ด๋™ํ•  ์ˆ˜ ์žˆ์–ด์š”.
  • FOUT (Flash of Unstyled Text): ์›น ํฐํŠธ ๋Œ€์‹  ์‹œ์Šคํ…œ ํฐํŠธ๋กœ ๋จผ์ € ํ…์ŠคํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๋‹ค๊ฐ€ ์›น ํฐํŠธ๊ฐ€ ๋กœ๋“œ๋˜๋ฉด ์›น ํฐํŠธ๋กœ ๊ต์ฒด๋˜๋Š” ํ˜„์ƒ์ด์—์š”. ํฐํŠธ ๊ต์ฒด ์‹œ ํ…์ŠคํŠธ์˜ ํฌ๊ธฐ๋‚˜ ์ค„ ๋†’์ด๊ฐ€ ๋‹ฌ๋ผ์ง€๋ฉด์„œ ๋ ˆ์ด์•„์›ƒ์ด ์ด๋™ํ•  ์ˆ˜ ์žˆ์–ด์š”.

2๏ธโƒฃ ๋™์ ์œผ๋กœ ์‚ฝ์ž…๋˜๋Š” ์ฝ˜ํ…์ธ  (๊ด‘๊ณ , ์œ„์ ฏ ๋“ฑ) ๐Ÿ“ข

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

3๏ธโƒฃ DOM ์—…๋ฐ์ดํŠธ๋กœ ์ธํ•œ ๋ ˆ์ด์•„์›ƒ ๋ณ€๊ฒฝ ๐Ÿ”„

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์— ์˜ํ•ด DOM ์š”์†Œ์˜ ์Šคํƒ€์ผ(ํŠนํžˆ width, height, margin, padding, top, left ๋“ฑ ๋ ˆ์ด์•„์›ƒ์— ์ง์ ‘์ ์ธ ์˜ํ–ฅ์„ ์ฃผ๋Š” ์†์„ฑ)์ด ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝ๋  ๋•Œ๋„ CLS๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด์š”. ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ ์—†์ด ๋ฐœ์ƒํ•˜๋ฉด CLS ์ ์ˆ˜์— ๋ฐ˜์˜๋ผ์š”.

๐Ÿ› ๏ธ Next.js/React์—์„œ CLS ์ตœ์ ํ™” ์ „๋žต

์ด์ œ Next.js์™€ React ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ CLS๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ์ค„์ด๋Š” ์‹ค์งˆ์ ์ธ ์ „๋žต๋“ค์„ ์‚ดํŽด๋ณผ๊ฒŒ์š”.

0๏ธโƒฃ ์ด๋ฏธ์ง€/๋ฏธ๋””์–ด ํฌ๊ธฐ ๋ช…์‹œํ•˜๊ธฐ ๐Ÿ“

์ด๋ฏธ์ง€๋‚˜ ๋น„๋””์˜ค ์š”์†Œ์˜ width์™€ height๋ฅผ ํ•ญ์ƒ ๋ช…์‹œํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ค‘์š”ํ•ด์š”.

<img> ํƒœ๊ทธ: width, height ์†์„ฑ

<!-- ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ์‹œ --> <img src="/my-image.jpg" alt="์„ค๋ช…" width="600" height="400" /> <!-- CLS ์œ ๋ฐœ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” ์˜ˆ์‹œ --> <img src="/my-image.jpg" alt="์„ค๋ช…" />

Next.js <Image> ์ปดํฌ๋„ŒํŠธ: width, height, fill ์†์„ฑ ํ™œ์šฉ โœจ

Next.js์˜ <Image> ์ปดํฌ๋„ŒํŠธ๋Š” ์ด๋ฏธ์ง€ ์ตœ์ ํ™”์™€ ํ•จ๊ป˜ CLS๋ฅผ ์ž๋™์œผ๋กœ ๋ฐฉ์ง€ํ•ด ์ฃผ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์˜ˆ์š”. width์™€ height๋ฅผ ๋ช…์‹œํ•˜๊ฑฐ๋‚˜, fill ๋ ˆ์ด์•„์›ƒ์„ ์‚ฌ์šฉํ•ด ๋ถ€๋ชจ ์š”์†Œ์— ๋งž์ถฐ ํฌ๊ธฐ๋ฅผ ์กฐ์ ˆํ•  ์ˆ˜ ์žˆ์–ด์š”.

// pages/index.tsx ๋˜๋Š” app/page.tsx import Image from 'next/image'; export default function HomePage() { return ( <div> <h1>Next.js CLS ์ตœ์ ํ™” ๊ฐ€์ด๋“œ</h1> <h2>0๏ธโƒฃ ๊ณ ์ •๋œ ํฌ๊ธฐ์˜ ์ด๋ฏธ์ง€</h2> {/* width์™€ height๋ฅผ ๋ช…์‹œํ•˜์—ฌ CLS ๋ฐฉ์ง€ */} <Image src="/images/example-fixed.jpg" alt="๊ณ ์ • ํฌ๊ธฐ ์ด๋ฏธ์ง€" width={600} // ์ด๋ฏธ์ง€์˜ ์‹ค์ œ ๋„ˆ๋น„ height={400} // ์ด๋ฏธ์ง€์˜ ์‹ค์ œ ๋†’์ด priority // LCP ๊ฐœ์„ ์„ ์œ„ํ•ด ์šฐ์„  ๋กœ๋“œ /> <p>์ด ์ด๋ฏธ์ง€๋Š” ๊ณ ์ •๋œ ํฌ๊ธฐ๋ฅผ ๊ฐ€์ ธ์š”.</p> <h2>1๏ธโƒฃ ๋ถ€๋ชจ ์š”์†Œ์— ๊ฝ‰ ์ฑ„์šฐ๋Š” ์ด๋ฏธ์ง€ (fill ๋ ˆ์ด์•„์›ƒ)</h2> <div style={{ position: 'relative', width: '100%', height: '300px' }}> {/* fill ๋ ˆ์ด์•„์›ƒ์œผ๋กœ ๋ถ€๋ชจ ์š”์†Œ์— ๋งž์ถฐ ํฌ๊ธฐ ์กฐ์ ˆ. ๋ถ€๋ชจ ์š”์†Œ์˜ ํฌ๊ธฐ ๋ช…์‹œ ํ•„์ˆ˜ */} <Image src="/images/example-fill.jpg" alt="๋ถ€๋ชจ ์š”์†Œ์— ์ฑ„์šฐ๋Š” ์ด๋ฏธ์ง€" fill // ๋ถ€๋ชจ ์š”์†Œ์— ๊ฝ‰ ์ฑ„์›€ style={{ objectFit: 'cover' }} // ๋ถ€๋ชจ ์š”์†Œ์— ๋งž์ถฐ ์ด๋ฏธ์ง€๋ฅผ ์ž๋ฆ„ sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" // ๋ฐ˜์‘ํ˜• ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ง€์ • /> </div> <p>์ด ์ด๋ฏธ์ง€๋Š” ๋ถ€๋ชจ ์š”์†Œ์— ๋งž์ถฐ ํฌ๊ธฐ๊ฐ€ ์กฐ์ ˆ๋ผ์š”.</p> </div> ); }
์œ ์šฉํ•œ ํŒ

<Image> ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ ์‹œ fill ์†์„ฑ์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ๋ฐ˜๋“œ์‹œ ๋ถ€๋ชจ ์š”์†Œ์— position: 'relative' ๋˜๋Š” position: 'absolute', position: 'fixed' ๋“ฑ์˜ ์†์„ฑ์„ ๋ถ€์—ฌํ•˜์—ฌ ์ปจํ…Œ์ด๋‹ ๋ธ”๋ก(Containing Block)์„ ๋งŒ๋“ค์–ด์ฃผ์…”์•ผ ํ•ด์š”.
๋˜ํ•œ, ๋ถ€๋ชจ ์š”์†Œ์˜ width์™€ height๋ฅผ ๋ช…ํ™•ํžˆ ์ง€์ •ํ•˜์—ฌ ์ด๋ฏธ์ง€๊ฐ€ ์ฐจ์ง€ํ•  ๊ณต๊ฐ„์„ ๋ฏธ๋ฆฌ ํ™•๋ณดํ•ด์•ผ CLS๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์–ด์š”.

1๏ธโƒฃ ์›น ํฐํŠธ ์ตœ์ ํ™”๋กœ ๋ ˆ์ด์•„์›ƒ ์‹œํ”„ํŠธ ๋ฐฉ์ง€ โœ๏ธ

์›น ํฐํŠธ ๋กœ๋”ฉ์œผ๋กœ ์ธํ•œ CLS๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ์ „๋žต๋“ค์„ ์‚ฌ์šฉํ•ด ๋ณด์„ธ์š”.

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

CSS @font-face ๊ทœ์น™์— font-display: swap์„ ์‚ฌ์šฉํ•˜๋ฉด ์›น ํฐํŠธ ๋กœ๋”ฉ ์ „๊นŒ์ง€ ์‹œ์Šคํ…œ ํฐํŠธ๋ฅผ ๋จผ์ € ๋ณด์—ฌ์ฃผ๊ณ , ์›น ํฐํŠธ๊ฐ€ ๋กœ๋“œ๋˜๋ฉด ๊ต์ฒดํ•ด์š”. FOIT๋ฅผ ๋ฐฉ์ง€ํ•˜์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ…์ŠคํŠธ๋ฅผ ๋น ๋ฅด๊ฒŒ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์–ด์š”.

/* styles/globals.css ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ CSS ๋ชจ๋“ˆ */ @font-face { font-family: 'MyCustomFont'; src: url('/fonts/MyCustomFont.woff2') format('woff2'); font-weight: normal; font-style: normal; font-display: swap; /* ๊ฐ€์žฅ ์ค‘์š”! */ } body { font-family: 'MyCustomFont', sans-serif; /* fallback ํฐํŠธ ์ง€์ • */ }
์ •๋ณด

font-display: swap์€ FOIT๋ฅผ ๋ฐฉ์ง€ํ•˜์ง€๋งŒ, ํฐํŠธ ๊ต์ฒด ์‹œ FOUT๋กœ ์ธํ•œ ๋ฏธ์„ธํ•œ ๋ ˆ์ด์•„์›ƒ ์‹œํ”„ํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ด๋ฅผ ๋”์šฑ ์ค„์ด๋ ค๋ฉด <link rel="preload">๋ฅผ ์‚ฌ์šฉํ•ด ํฐํŠธ๋ฅผ ๋ฏธ๋ฆฌ ๋กœ๋“œํ•˜๊ฑฐ๋‚˜, CSS size-adjust, ascent-override, descent-override, line-gap-override ๋“ฑ์˜ ์†์„ฑ์„ ํ™œ์šฉํ•˜์—ฌ fallback ํฐํŠธ์™€ ์›น ํฐํŠธ์˜ ํฌ๊ธฐ๋ฅผ ์ตœ๋Œ€ํ•œ ์ผ์น˜์‹œํ‚ค๋Š” ๋…ธ๋ ฅ์ด ํ•„์š”ํ•ด์š”.

Next.js next/font ํ™œ์šฉ

Next.js 13๋ถ€ํ„ฐ๋Š” next/font ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜์—ฌ ํฐํŠธ ๋กœ๋”ฉ์„ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ด ๋ชจ๋“ˆ์€ ํฐํŠธ ํŒŒ์ผ์„ ์ž๋™์œผ๋กœ ์ตœ์ ํ™”ํ•˜๊ณ , ๋ ˆ์ด์•„์›ƒ ์‹œํ”„ํŠธ๋ฅผ ์ตœ์†Œํ™”ํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค˜์š”.

// app/layout.tsx (App Router ๊ธฐ์ค€) import { Inter, Noto_Sans_KR } from 'next/font/google'; // Google Fonts ์‚ฌ์šฉ ์˜ˆ์‹œ const inter = Inter({ subsets: ['latin'] }); const notoSansKr = Noto_Sans_KR({ subsets: ['latin'], weight: ['400', '700'], // ์‚ฌ์šฉํ•  ํฐํŠธ ๋‘๊ป˜ ์ง€์ • display: 'swap', // CLS ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ font-display }); export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="ko" className={notoSansKr.className}> {/* HTML ํƒœ๊ทธ์— ํฐํŠธ ํด๋ž˜์Šค ์ ์šฉ */} <body>{children}</body> </html> ); }
์œ ์šฉํ•œ ํŒ

next/font๋Š” ํฐํŠธ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด์š”. ํŠนํžˆ ๋กœ์ปฌ ํฐํŠธ ์‚ฌ์šฉ ์‹œ์—๋„ next/font/local์„ ํ™œ์šฉํ•˜์—ฌ ํฐํŠธ ํŒŒ์ผ์„ ๋ฒˆ๋“ค์— ํฌํ•จํ•˜๊ณ  ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.

2๏ธโƒฃ ๋™์  ์ฝ˜ํ…์ธ ๋ฅผ ์œ„ํ•œ ๊ณต๊ฐ„ ์˜ˆ์•ฝ โ†”๏ธ

๊ด‘๊ณ , ์ž„๋ฒ ๋“œ ์ฝ˜ํ…์ธ , ๋™์ ์œผ๋กœ ๋กœ๋“œ๋˜๋Š” UI ์š”์†Œ ๋“ฑ์€ ๋ฏธ๋ฆฌ ๊ณต๊ฐ„์„ ์˜ˆ์•ฝํ•ด ๋‘๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ด์š”.

min-height ๋˜๋Š” aspect-ratio ์†์„ฑ ์‚ฌ์šฉ

์ฝ˜ํ…์ธ ๊ฐ€ ๋กœ๋“œ๋  ์˜์—ญ์— ์ตœ์†Œ ๋†’์ด๋ฅผ ์ง€์ •ํ•˜๊ฑฐ๋‚˜, aspect-ratio๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ€๋กœ์„ธ๋กœ ๋น„์œจ์„ ๊ณ ์ •ํ•˜๋ฉด ์ฝ˜ํ…์ธ ๊ฐ€ ๋กœ๋“œ๋œ ํ›„์—๋„ ๋ ˆ์ด์•„์›ƒ ์‹œํ”„ํŠธ ์—†์ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ฐฐ์น˜๋ผ์š”.

// components/AdBanner.tsx export default function AdBanner() { return ( // ๊ด‘๊ณ ๊ฐ€ ๋กœ๋“œ๋  ๊ณต๊ฐ„์„ ๋ฏธ๋ฆฌ 300px ๋†’์ด๋กœ ์˜ˆ์•ฝ <div style={{ minHeight: '300px', backgroundColor: '#f0f0f0', display: 'flex', alignItems: 'center', justifyContent: 'center', border: '1px dashed #ccc' }}> <p>๊ด‘๊ณ  ๋กœ๋”ฉ ์ค‘...</p> {/* ์‹ค์ œ ๊ด‘๊ณ  ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์—ฌ๊ธฐ์— ๋กœ๋“œ๋  ๊ฑฐ์˜ˆ์š” */} </div> ); } // components/VideoEmbed.tsx export default function VideoEmbed({ videoId }: { videoId: string }) { return ( // 16:9 ๋น„์œจ๋กœ ๊ณต๊ฐ„ ์˜ˆ์•ฝ <div style={{ width: '100%', aspectRatio: '16 / 9', backgroundColor: '#000', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <iframe width="100%" height="100%" src={`https://www.youtube.com/embed/${videoId}`} frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen title="YouTube video player" style={{ position: 'absolute', top: 0, left: 0 }} // iframe์ด ๋ถ€๋ชจ div์— ๊ฝ‰ ์ฐจ๋„๋ก ></iframe> </div> ); }

์Šค์ผˆ๋ ˆํ†ค UI (Skeleton UI) ํ™œ์šฉ ๐Ÿ’€

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

// components/ArticleCard.tsx import React, { useState, useEffect } from 'react'; interface Article { id: number; title: string; content: string; } function ArticleSkeleton() { return ( <div style={{ border: '1px solid #eee', padding: '15px', borderRadius: '8px', marginBottom: '15px' }}> <div style={{ width: '80%', height: '24px', backgroundColor: '#e0e0e0', marginBottom: '10px', borderRadius: '4px' }}></div> <div style={{ width: '100%', height: '16px', backgroundColor: '#e0e0e0', marginBottom: '8px', borderRadius: '4px' }}></div> <div style={{ width: '90%', height: '16px', backgroundColor: '#e0e0e0', borderRadius: '4px' }}></div> </div> ); } export default function ArticleCard({ articleId }: { articleId: number }) { const [article, setArticle] = useState<Article | null>(null); const [loading, setLoading] = useState(true); useEffect(() => { const fetchArticle = async () => { setLoading(true); // ๊ฐ€์ƒ์˜ API ํ˜ธ์ถœ ์ง€์—ฐ await new Promise(resolve => setTimeout(resolve, 1500)); setArticle({ id: articleId, title: `๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ ์ œ๋ชฉ ${articleId}`, content: `์ด๊ฒƒ์€ ${articleId}๋ฒˆ ํฌ์ŠคํŠธ์˜ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์—๊ฒŒ ์œ ์ตํ•œ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ์–ด์š”.` }); setLoading(false); }; fetchArticle(); }, [articleId]); if (loading) { return <ArticleSkeleton />; } if (!article) { return <p>์•„ํ‹ฐํด์„ ์ฐพ์„ ์ˆ˜ ์—†์–ด์š”.</p>; } return ( <div style={{ border: '1px solid #ccc', padding: '15px', borderRadius: '8px', marginBottom: '15px' }}> <h3>{article.title}</h3> <p>{article.content}</p> </div> ); }

3๏ธโƒฃ ๋ ˆ์ด์•„์›ƒ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋Š” CSS ์• ๋‹ˆ๋ฉ”์ด์…˜ โœจ

์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๊ตฌํ˜„ํ•  ๋•Œ๋Š” width, height, margin, padding, top, left ๋“ฑ ๋ ˆ์ด์•„์›ƒ์— ์ง์ ‘์ ์ธ ์˜ํ–ฅ์„ ์ฃผ๋Š” ์†์„ฑ ๋Œ€์‹  transform (์ด๋™, ํฌ๊ธฐ ์กฐ์ ˆ, ํšŒ์ „)์ด๋‚˜ opacity (ํˆฌ๋ช…๋„) ์†์„ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์•„์š”. ์ด ์†์„ฑ๋“ค์€ ๋ธŒ๋ผ์šฐ์ €์˜ ํ•ฉ์„ฑ ๋ ˆ์ด์–ด์—์„œ ์ฒ˜๋ฆฌ๋˜์–ด ๋ฆฌํ”Œ๋กœ์šฐ(Reflow)๋ฅผ ์œ ๋ฐœํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— CLS๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์–ด์š”.

/* CLS๋ฅผ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ๋Š” ์˜ˆ์‹œ */ .bad-animation { transition: width 0.3s ease-in-out; width: 100px; } .bad-animation:hover { width: 200px; /* ์ฃผ๋ณ€ ์š”์†Œ๊ฐ€ ๋ฐ€๋ ค๋‚˜์š” */ } /* CLS๋ฅผ ๋ฐฉ์ง€ํ•˜๋Š” ์ข‹์€ ์˜ˆ์‹œ */ .good-animation { transition: transform 0.3s ease-in-out; transform: translateX(0); } .good-animation:hover { transform: translateX(20px); /* ๋ ˆ์ด์•„์›ƒ ์ด๋™ ์—†์ด ์š”์†Œ๋งŒ ์›€์ง์—ฌ์š” */ }

๐Ÿ“Š CLS ์ธก์ • ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง

CLS๋ฅผ ๊ฐœ์„ ํ•˜๋ ค๋ฉด ํ˜„์žฌ ์ƒํƒœ๋ฅผ ์ •ํ™•ํžˆ ์ธก์ •ํ•˜๊ณ  ์ง€์†์ ์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ด์š”.

0๏ธโƒฃ ๊ฐœ๋ฐœ ์ค‘ CLS ํ™•์ธ ๐Ÿ› ๏ธ

  • Chrome DevTools: ์„ฑ๋Šฅ ํƒญ์—์„œ "Layout Shifts"๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”. ๋ ˆ์ด์•„์›ƒ ์‹œํ”„ํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ ์˜์—ญ์„ ์‹œ๊ฐ์ ์œผ๋กœ ๋ณด์—ฌ์ฃผ์–ด ์›์ธ์„ ํŒŒ์•…ํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค˜์š”.
  • Lighthouse: Chrome DevTools์— ๋‚ด์žฅ๋œ Lighthouse ๊ฐ์‚ฌ ๋„๊ตฌ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด CLS ์ ์ˆ˜๋ฅผ ํฌํ•จํ•œ Core Web Vitals ์ ์ˆ˜๋ฅผ ์ž์„ธํžˆ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”. ๊ฐœ์„  ๋ฐฉ์•ˆ๋„ ํ•จ๊ป˜ ์ œ์‹œํ•ด ์ค€๋‹ต๋‹ˆ๋‹ค.

1๏ธโƒฃ ์‹ค์ œ ์‚ฌ์šฉ์ž ํ™˜๊ฒฝ์—์„œ์˜ ๋ชจ๋‹ˆํ„ฐ๋ง (RUM) ๐Ÿ“ˆ

  • PageSpeed Insights: ์›น ํŽ˜์ด์ง€์˜ ์‹ค์ œ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ(Field Data)์™€ ์‹คํ—˜์‹ค ๋ฐ์ดํ„ฐ(Lab Data)๋ฅผ ๋ชจ๋‘ ์ œ๊ณตํ•˜์—ฌ CLS ๋ฌธ์ œ๋ฅผ ์ง„๋‹จํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ด์š”.
  • Google Search Console: "ํ•ต์‹ฌ ์›น ๋ฐ”์ดํƒˆ" ๋ณด๊ณ ์„œ์—์„œ ์›น์‚ฌ์ดํŠธ์˜ CLS ์ ์ˆ˜๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ์ˆ˜ ์žˆ์–ด์š”.
  • Web-vitals ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: web-vitals JavaScript ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์ œ ์‚ฌ์šฉ์ž ํ™˜๊ฒฝ์—์„œ CLS๋ฅผ ํฌํ•จํ•œ Core Web Vitals ์ง€ํ‘œ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์— ๋ฏธ์น˜๋Š” ์‹ค์ œ ์˜ํ–ฅ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.
// pages/_app.tsx ๋˜๋Š” app/layout.tsx ์—์„œ web-vitals ์‚ฌ์šฉ ์˜ˆ์‹œ import type { AppProps } from 'next/app'; import { reportWebVitals } from 'web-vitals'; function MyApp({ Component, pageProps }: AppProps) { return <Component {...pageProps} />; } // Next.js์—์„œ ์ œ๊ณตํ•˜๋Š” reportWebVitals ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ง€ํ‘œ๋ฅผ ์ฝ˜์†”์— ์ถœ๋ ฅํ•˜๊ฑฐ๋‚˜ ์„œ๋ฒ„๋กœ ์ „์†กํ•  ์ˆ˜ ์žˆ์–ด์š”. export function reportWebVitals(metric: any) { console.log(metric); // ๊ฐœ๋ฐœ ์ค‘์—๋Š” ์ฝ˜์†”์— ์ถœ๋ ฅํ•˜์—ฌ ํ™•์ธ // ์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋Š” GA ๋˜๋Š” ๋‹ค๋ฅธ ๋ถ„์„ ํˆด๋กœ ์ „์†ก // const body = JSON.stringify(metric); // navigator.sendBeacon('/api/web-vitals', body); } export default MyApp;
์œ ์šฉํ•œ ํŒ

web-vitals ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” CLS ์™ธ์—๋„ LCP, FID ๋“ฑ ๋‹ค์–‘ํ•œ Core Web Vitals ์ง€ํ‘œ๋ฅผ ์ธก์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค˜์š”. ์‹ค์ œ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ตœ์ ํ™” ๋ฐฉํ–ฅ์„ ์„ค์ •ํ•˜๋Š” ๋ฐ ๋งค์šฐ ์œ ์šฉํ•˜๋‹ˆ ๊ผญ ํ™œ์šฉํ•ด ๋ณด์„ธ์š”.

๐Ÿ“ ์ •๋ฆฌํ•˜๋ฉฐ

์˜ค๋Š˜์€ Next.js์™€ React ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ Cumulative Layout Shift(CLS)๊ฐ€ ๋ฌด์—‡์ธ์ง€, ์™œ ์ค‘์š”ํ•œ์ง€, ๊ทธ๋ฆฌ๊ณ  ์–ด๋–ป๊ฒŒ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์ž์„ธํžˆ ์•Œ์•„๋ดค์–ด์š”. CLS๋Š” ๋‹จ์ˆœํžˆ ์ ์ˆ˜๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์„ ๋„˜์–ด, ์‚ฌ์šฉ์ž์—๊ฒŒ ๋” ์•ˆ์ •์ ์ด๊ณ  ์พŒ์ ํ•œ ์›น ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๋Š” ๋ฐ ํ•„์ˆ˜์ ์ธ ์š”์†Œ์˜ˆ์š”.
ํ•ต์‹ฌ์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฝ˜ํ…์ธ ์˜ ์ตœ์ข… ํฌ๊ธฐ๋ฅผ ์˜ˆ์ธกํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ฏธ๋ฆฌ ๊ณต๊ฐ„์„ ํ™•๋ณดํ•ด ์ฃผ๋Š” ๊ฒƒ์ด์—ˆ์–ด์š”. ์ด๋ฏธ์ง€/๋ฏธ๋””์–ด์— ํฌ๊ธฐ๋ฅผ ๋ช…์‹œํ•˜๊ณ , ์›น ํฐํŠธ ๋กœ๋”ฉ ์ „๋žต์„ ์„ธ์šฐ๊ณ , ๋™์  ์ฝ˜ํ…์ธ ๋ฅผ ์œ„ํ•œ ๊ณต๊ฐ„์„ ์˜ˆ์•ฝํ•˜๋ฉฐ, ๋ ˆ์ด์•„์›ƒ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ด์š”.
CLS ์ตœ์ ํ™”๋Š” ํ•œ ๋ฒˆ์œผ๋กœ ๋๋‚˜๋Š” ์ž‘์—…์ด ์•„๋‹ˆ์—์š”. ์ง€์†์ ์œผ๋กœ ์ธก์ •ํ•˜๊ณ , ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ˜์˜ํ•˜๋ฉฐ ๊ฐœ์„ ํ•ด ๋‚˜๊ฐ€๋Š” ๋…ธ๋ ฅ์ด ํ•„์š”ํ•˜๋‹ต๋‹ˆ๋‹ค. ์ด ๊ธ€์ด ์—ฌ๋Ÿฌ๋ถ„์˜ Next.js/React ํ”„๋กœ์ ํŠธ์—์„œ CLS๋ฅผ ๊ฐœ์„ ํ•˜๊ณ , ๋” ๋‚˜์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๋Š” ๋ฐ ์‹ค์งˆ์ ์ธ ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ผ์š”. ํ•จ๊ป˜ ๋” ์ข‹์€ ์›น์„ ๋งŒ๋“ค์–ด๋‚˜๊ฐ€์š”!

๐Ÿ“ฎ ์ฐธ๊ณ 

์—ฐ๊ด€๋œ ํฌ์ŠคํŠธ