๐ผ๏ธ๐ข ๋ฏธ์ ์ฉ ์ด๋ฏธ์ง ์ต์ ํ: Core Web Vitals์ ์ ํด
Summary
๋ถํ์ํ๊ฒ ํฐ ์ด๋ฏธ์ง ํ์ผ, ๋นํจ์จ์ ์ธ ํ์, ๋ฐ์ํ ์ด๋ฏธ์ง ๋ฏธ์ ์ฉ, ์ง์ฐ ๋ก๋ฉ ๋ถ์ฌ๋ ์น ์ฑ๋ฅ ์ ํ์ Core Web Vitals ์ ์๋ฅผ ๋ฎ์ถ๋ ์ฃผ๋ฒ์
๋๋ค. WebP/AVIF, srcset
/sizes
, loading="lazy"
, rel="preload"
๋ฑ์ ์ต์ ํ ๊ธฐ๋ฒ์ ์ ์ฉํ์ฌ ๋น ๋ฅด๊ณ ํจ์จ์ ์ธ ์ด๋ฏธ์ง ๋ก๋ฉ์ ๊ตฌํํด์ผ ํฉ๋๋ค.
Why Wrong?
์น ํ์ด์ง์ ์ต์ ํ๋์ง ์์ ์ด๋ฏธ์ง๋ฅผ ์ฌ์ฉํ๋ฉด ์ฌ์ฉ์ ๊ฒฝํ๊ณผ ์น ์ฑ๋ฅ์ ์ฌ๊ฐํ ์ ์ํฅ์ ๋ฏธ์นฉ๋๋ค. ํนํ ์ด๋ฏธ์ง์ ๊ณผ๋ํ ํ์ผ ํฌ๊ธฐ, ๋นํจ์จ์ ์ธ ํ์(JPG, PNG ๋์ WebP, AVIF ๋ฑ), ๋ฐ์ํ ์ด๋ฏธ์ง ๋ฏธ์ ์ฉ, ๊ทธ๋ฆฌ๊ณ ์ง์ฐ ๋ก๋ฉ(Lazy Loading)์ ๋ถ์ฌ๋ ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ๋ฅผ ์ผ๊ธฐํฉ๋๋ค:
- ๋๋ฆฐ ๋ก๋ฉ ์๋: ์ด๋ฏธ์ง๋ ์น ํ์ด์ง ์ ์ฒด ์ฉ๋์ ์๋น ๋ถ๋ถ์ ์ฐจ์งํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ์ต์ ํ๋์ง ์์ ์ด๋ฏธ์ง๋ ๋ค์ด๋ก๋ ์๊ฐ์ ๊ธธ๊ฒ ๋ง๋ค์ด ํ์ด์ง ๋ก๋ฉ์ ์ง์ฐ์ํค๊ณ , ์ฌ์ฉ์์๊ฒ ๋ต๋ตํจ์ ์ค๋๋ค.
- Core Web Vitals ์ ํด: ํนํ '๊ฐ์ฅ ํฐ ์ฝํ ์ธ ํ์ธํธ(Largest Contentful Paint, LCP)' ์งํ์ ์ง์ ์ ์ธ ์ํฅ์ ๋ฏธ์นฉ๋๋ค. LCP๋ ๋ทฐํฌํธ ๋ด ๊ฐ์ฅ ํฐ ์ฝํ ์ธ ์์๊ฐ ๋ ๋๋ง๋๋ ์๊ฐ์ ์ธก์ ํ๋๋ฐ, ํฐ ์ด๋ฏธ์ง๋ ์ด ์๊ฐ์ ๋๋ ค ๊ฒ์ ์์ง ์์ ๋ฐ ์ฌ์ฉ์ ๊ฒฝํ ์ ์๋ฅผ ๋ฎ์ถฅ๋๋ค.
- ๋คํธ์ํฌ ๋์ญํญ ๋ญ๋น: ์ฌ์ฉ์์ ๊ธฐ๊ธฐ(๋ชจ๋ฐ์ผ ๋ฑ)์ ๊ด๊ณ์์ด ํญ์ ๊ฐ์ฅ ํฐ ์ด๋ฏธ์ง๋ฅผ ๋ค์ด๋ก๋ํ๊ฒ ํ์ฌ ๋ถํ์ํ ๋ฐ์ดํฐ ์๋ชจ์ ๋คํธ์ํฌ ์์ ๋ญ๋น๋ฅผ ์ด๋ํฉ๋๋ค.
- CLS (Cumulative Layout Shift) ๋ฐ์ ๊ฐ๋ฅ์ฑ: ์ด๋ฏธ์ง์
width
์height
์์ฑ์ด ๋ช ์๋์ง ์์ ์ด๋ฏธ์ง๊ฐ ๋ก๋ฉ๋ ํ ๋ ์ด์์์ด ๋ฐ๋ฆฌ๋ ํ์(CLS)์ด ๋ฐ์ํ ์ ์์ต๋๋ค. ์ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ๋ฐฉํดํ๋ ์ฃผ์ ์์ ์ค ํ๋์ ๋๋ค. - ์๋ฒ ๋น์ฉ ์ฆ๊ฐ: ๋ถํ์ํ๊ฒ ํฐ ์ด๋ฏธ์ง๋ ์๋ฒ ํธ๋ํฝ ์ฆ๊ฐ๋ก ์ด์ด์ ธ ํธ์คํ ๋น์ฉ์ ์ฆ๊ฐ์ํฌ ์ ์์ต๋๋ค.
How to Fix?
์ด๋ฏธ์ง ์ต์ ํ๋ ์น ์ฑ๋ฅ ๊ฐ์ ์ ํต์ฌ ์์์ ๋๋ค. ๋ค์ ์ ๋ต๋ค์ ํตํด ํจ๊ณผ์ ์ธ ์ด๋ฏธ์ง ๋ก๋ฉ์ ๊ตฌํํ ์ ์์ต๋๋ค:
- ์ ์ ํ ์ด๋ฏธ์ง ํ์ ์ฌ์ฉ: JPG, PNG ๋์ WebP, AVIF์ ๊ฐ์ ํ๋์ ์ด๊ณ ํจ์จ์ ์ธ ์ด๋ฏธ์ง ํ์์ ์ฐ์ ์ ์ผ๋ก ๊ณ ๋ คํฉ๋๋ค. ์ด ํ์๋ค์ ๋ ์์ ํ์ผ ํฌ๊ธฐ๋ก ๋ ๋์ ์์ถ๋ฅ ๊ณผ ํ์ง์ ์ ๊ณตํฉ๋๋ค.
- ๋ฐ์ํ ์ด๋ฏธ์ง ๊ตฌํ:
<picture>
์์์srcset
,sizes
์์ฑ์ ํ์ฉํ์ฌ ์ฌ์ฉ์ ๊ธฐ๊ธฐ์ ๋ทฐํฌํธ ํฌ๊ธฐ์ ๋ฐ๋ผ ์ต์ ํ๋ ์ด๋ฏธ์ง๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด๋ ๋ถํ์ํ ๋ค์ด๋ก๋๋ฅผ ๋ฐฉ์งํ๊ณ ๋์ญํญ์ ์ ์ฝํฉ๋๋ค. - ์ง์ฐ ๋ก๋ฉ (Lazy Loading): ๋ทฐํฌํธ ๋ฐ์ ์๋ ์ด๋ฏธ์ง๋
loading="lazy"
์์ฑ์ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์๊ฐ ์คํฌ๋กคํ์ฌ ํด๋น ์ด๋ฏธ์ง๊ฐ ๋ทฐํฌํธ์ ๋ค์ด์ฌ ๋๋ง ๋ก๋๋๋๋ก ํฉ๋๋ค. ์ด๋ ์ด๊ธฐ ํ์ด์ง ๋ก๋ฉ ์๋๋ฅผ ํฌ๊ฒ ํฅ์์ํต๋๋ค. - LCP ์ด๋ฏธ์ง ๋ฏธ๋ฆฌ ๋ก๋ (Preloading): ํ์ด์ง์์ ๊ฐ์ฅ ํฐ ์ฝํ
์ธ (์ฃผ๋ก ํ์ด๋ก ์ด๋ฏธ์ง)๋ก ์๋ณ๋ ์ด๋ฏธ์ง๋
<link rel="preload" href="..." as="image">
๋ฅผ<head>
์ ์ถ๊ฐํ์ฌ ๋ธ๋ผ์ฐ์ ๊ฐ ๋ค๋ฅธ ๋ฆฌ์์ค๋ณด๋ค ๋จผ์ ๋ก๋ํ๋๋ก ์ง์ํฉ๋๋ค. ์ด๋ LCP๋ฅผ ๊ฐ์ ํ๋ ๋ฐ ์ค์ํฉ๋๋ค. - ์ด๋ฏธ์ง ํฌ๊ธฐ ๋ช
์:
<img>
ํ๊ทธ์width
์height
์์ฑ์ ๋ช ์ํ์ฌ ์ด๋ฏธ์ง๊ฐ ๋ก๋๋๊ธฐ ์ ์๋ ๋ธ๋ผ์ฐ์ ๊ฐ ์ด๋ฏธ์ง์ ๊ณต๊ฐ์ ๋ฏธ๋ฆฌ ํ๋ณดํ๊ฒ ํ์ฌ CLS๋ฅผ ๋ฐฉ์งํฉ๋๋ค. - ์ด๋ฏธ์ง ์์ถ ๋ฐ ์ต์ ํ ๋๊ตฌ ์ฌ์ฉ: ์ด๋ฏธ์ง ํธ์ง ์ํํธ์จ์ด, ์จ๋ผ์ธ ์ต์ ํ ๋๊ตฌ(TinyPNG, Squoosh ๋ฑ), ๋๋ ๋น๋ ๋๊ตฌ(webpack์
image-minimizer-webpack-plugin
๋ฑ)๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ต์ ์ ํ์ง๊ณผ ํฌ๊ธฐ๋ก ์์ถํฉ๋๋ค. - CDN (Content Delivery Network) ํ์ฉ: ์ด๋ฏธ์ง CDN์ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์์ ์ง๋ฆฌ์ ์ผ๋ก ๊ฐ๊น์ด ์๋ฒ์์ ์ด๋ฏธ์ง๋ฅผ ์ ๊ณตํ๊ณ , ์ด๋ฏธ์ง ํฌ๋งท ๋ณํ, ํฌ๊ธฐ ์กฐ์ , ์บ์ฑ ๋ฑ์ ์๋์ผ๋ก ์ฒ๋ฆฌํ๋๋ก ํฉ๋๋ค.
Before Code (Bad)
```html
<!-- ์ต์ ํ๋์ง ์์ ์ด๋ฏธ์ง ๋ก๋ฉ ์์ -->
<img src="./large-unoptimized-hero-image.jpg" alt="์๋ฆ๋ค์ด ํ๊ฒฝ ์ฌ์ง" style="max-width: 100%;">
<div style="height: 1000px;">์คํฌ๋กคํด์ผ ๋ณด์ด๋ ์ฝํ
์ธ </div>
<img src="./another-large-image-below-fold.png" alt="๋ฐ๋ท๊ฐ ํ๊ฒฝ">
```
**๋ฌธ์ ์ :**
* `large-unoptimized-hero-image.jpg`๋ ์์ชฝ์ ์์ด LCP์ ์ง์ ์ ์ธ ์ํฅ์ ์ฃผ์ง๋ง, ์ต์ ํ๋์ง ์์ ํฌ๊ธฐ์ ํฌ๋งท์ผ๋ก ์ธํด ๋ก๋ฉ์ด ์ง์ฐ๋ ์ ์์ต๋๋ค.
* `another-large-image-below-fold.png`๋ ๋ทฐํฌํธ ์๋์ ์์ง๋ง ์ฆ์ ๋ก๋๋์ด ์ด๊ธฐ ๋ก๋ฉ ์ฑ๋ฅ์ ๋ถํ์ํ ๋ถ๋ด์ ์ค๋๋ค.
* ๋ ์ด๋ฏธ์ง ๋ชจ๋ `width`์ `height` ์์ฑ์ด ์์ด CLS๋ฅผ ์ ๋ฐํ ์ ์์ต๋๋ค.
* ๋ฐ์ํ ์ด๋ฏธ์ง๋ฅผ ์ํ `srcset`/`sizes`๊ฐ ์์ด ๋ชจ๋ ์ฌ์ฉ์์๊ฒ ๋์ผํ ํฌ๊ธฐ์ ์ด๋ฏธ์ง๋ฅผ ์ ๊ณตํฉ๋๋ค.
After Code (Good)
```html
<!-- ์ต์ ํ๋ ์ด๋ฏธ์ง ๋ก๋ฉ ์์ -->
<head>
<!-- LCP ์ด๋ฏธ์ง ๋ฏธ๋ฆฌ ๋ก๋: ๋ทฐํฌํธ ๋ด ๊ฐ์ฅ ํฐ ์ด๋ฏธ์ง๋ ๋น ๋ฅด๊ฒ ๋ก๋๋๋๋ก ์ฐ์ ์์๋ฅผ ๋์ฌ์ค๋๋ค. -->
<link rel="preload" fetchpriority="high" as="image" href="./optimized-hero-image.webp" type="image/webp">
</head>
<body>
<!-- ๋ฐ์ํ ๋ฐ ์ต์ ํ๋ ํ์ด๋ก ์ด๋ฏธ์ง -->
<picture>
<!-- WebP ์ง์ ๋ธ๋ผ์ฐ์ -->
<source srcset="./optimized-hero-image.webp 1x, ./optimized-hero-image@2x.webp 2x" type="image/webp">
<!-- JPG ํด๋ฐฑ: ๋ค๋ฅธ ๋ธ๋ผ์ฐ์ ์ฉ -->
<img src="./optimized-hero-image.jpg"
srcset="./optimized-hero-image.jpg 1x, ./optimized-hero-image@2x.jpg 2x"
alt="์๋ฆ๋ค์ด ํ๊ฒฝ ์ฌ์ง"
width="1200" height="800"
loading="eager"> <!-- LCP ์ด๋ฏธ์ง์ด๋ฏ๋ก 'eager' (๊ธฐ๋ณธ๊ฐ) -->
</picture>
<div style="height: 1000px;">์คํฌ๋กคํด์ผ ๋ณด์ด๋ ์ฝํ
์ธ </div>
<!-- ๋ทฐํฌํธ ์๋ ์ด๋ฏธ์ง ์ง์ฐ ๋ก๋ (Lazy Loading) -->
<picture>
<source srcset="./optimized-beach-image.webp 1x, ./optimized-beach-image@2x.webp 2x" type="image/webp">
<img src="./optimized-beach-image.jpg"
srcset="./optimized-beach-image.jpg 1x, ./optimized-beach-image@2x.jpg 2x"
alt="๋ฐ๋ท๊ฐ ํ๊ฒฝ"
width="800" height="600"
loading="lazy"> <!-- ๋ทฐํฌํธ ๋ฐ์ ์์ผ๋ฏ๋ก 'lazy' -->
</picture>
</body>
```
**๊ฐ์ ์ :**
* `optimized-hero-image.webp`๋ฅผ `preload`ํ์ฌ LCP ์ฑ๋ฅ์ ๊ทน๋ํํฉ๋๋ค.
* `<picture>`์ `srcset`์ ์ฌ์ฉํ์ฌ ๋ธ๋ผ์ฐ์ ๊ฐ ์ต์ ์ ์ด๋ฏธ์ง ํ์(WebP)๊ณผ ํด์๋๋ฅผ ์ ํํ๊ฒ ํฉ๋๋ค.
* `width`์ `height`๋ฅผ ๋ช
์ํ์ฌ CLS๋ฅผ ๋ฐฉ์งํ๊ณ ๋ ์ด์์ ์์ ์ฑ์ ํ๋ณดํฉ๋๋ค.
* `loading="lazy"`๋ฅผ ์ฌ์ฉํ์ฌ ๋ทฐํฌํธ ์๋์ ์ด๋ฏธ์ง๋ ํ์ํ ๋๋ง ๋ก๋๋๋๋ก ํฉ๋๋ค. ์ด๋ ์ด๊ธฐ ๋ก๋ฉ ์ฑ๋ฅ๊ณผ ๋์ญํญ ์ฌ์ฉ๋์ ์ค์ฌ์ค๋๋ค.
* ์ด๋ฏธ์ง ํ์ผ ์์ฒด๋ฅผ ์์ถํ๊ณ , WebP/AVIF์ ๊ฐ์ ํจ์จ์ ์ธ ํฌ๋งท์ผ๋ก ๋ณํํ์ฌ ํ์ผ ํฌ๊ธฐ๋ฅผ ์ต์ํํด์ผ ํฉ๋๋ค. (์ฝ๋ ์์์์๋ ํ์ผ๋ช
์ผ๋ก ํํ)