๐ผ๏ธ 06. ์ด๋ฏธ์ง & ๋ฏธ๋์ด ์ต์ ํ: ์์ ๋, ๋ก๋ฉ์ด ์ ์ด๋ ๊ฒ ๋๋ ค์?
๐ ๊ฐ์
img alt, srcset/sizes, picture ์ํธ ๋๋ ์ , lazy loading, Next.js Image ์ปดํฌ๋ํธ โ ์ด๋ฏธ์ง๊ฐ ์ฑ๋ฅ์ ์ข์ฐํ๋ ์ด์ ๋ฅผ ํํค์นฉ๋๋ค.
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 18๋ถ / ํต์ฌ ํํธ๋ง: 10๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
[img์ ํ์ ์์ฑ] โ [srcset/sizes ๋ฐ์ํ ์ด๋ฏธ์ง] โ [picture ์ํธ ๋๋ ์ ] โ [lazy loading] โ [Next.js Image]
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
-
alt์์ฑ์ ์ํฉ๋ณ ์ฌ๋ฐ๋ฅธ ์์ฑ ๋ฐฉ๋ฒ์ ์ค๋ช ํ ์ ์๋ค. -
srcset+sizes๋ก ํด์๋๋ณ ์ต์ ์ด๋ฏธ์ง๋ฅผ ์ ๊ณตํ ์ ์๋ค. -
<picture>๋ก ์ํธ ๋๋ ์ (๋ชจ๋ฐ์ผ/๋ฐ์คํฌํ ๋ค๋ฅธ ์ด๋ฏธ์ง) ์ ๊ตฌํํ ์ ์๋ค. - Next.js
<Image>๊ฐ<img>์ ์ด๋ป๊ฒ ๋ค๋ฅธ์ง ์ค๋ช ํ ์ ์๋ค.
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ๐ฃ ์์ฒ ( ์ ์ ): "์ํธ ๋, ๋ชจ๋ฐ์ผ์์ ์ธ๋ค์ผ์ด ์์ฒญ ๋๋ฆฌ๊ฒ ๋ก๋๋ผ์. ๋ณด๋๊น ๋ฐ์คํฌํ์ฉ 4000px ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋๋ก ๋ชจ๋ฐ์ผ์๋ ์ฐ๊ณ ์์ด์ ๊ทธ๋ฐ ๊ฑฐ ๊ฐ์์. ์ด๋ป๊ฒ ํด์ผ ํด์?"
- ๐ฆ ์ํธ ( ๋ฆฌ๋ ): "์ด๋ฏธ์ง๊ฐ ์น ์ฑ๋ฅ์ 30~40%๋ฅผ ์ข์ฐํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์์. ์ง๊ธ ๋ชจ๋ฐ์ผ์์ 4000px์ง๋ฆฌ 5MB ์ด๋ฏธ์ง๋ฅผ 150px๋ก ์ถ์ํด์ ๋ณด์ฌ์ฃผ๋ ๊ฑด 5MB ๋ฐ์ดํฐ๋ฅผ ์ฐ๋ ๊ธฐํต์ ๋ฒ๋ฆฌ๋ ๊ฑฐ์์.
srcset์ผ๋ก ๊ธฐ๊ธฐ์ ๋ง๋ ์ด๋ฏธ์ง๋ฅผ ์ฃผ๊ณ ,loading='lazy'๋ก ๋ทฐํฌํธ ๋ฐ ์ด๋ฏธ์ง ๋ก๋๋ฅผ ๋ฏธ๋ค์ผ ํด์."
๐ผ๏ธ 1. img ํ์ ์์ฑ
<!-- โ
5๋
์ฐจ์ img ๊ธฐ๋ณธ ๊ตฌ์กฐ -->
<img
src="/images/profile.jpg" <!-- ํด๋ฐฑ URL (srcset ๋ฏธ์ง์ ๋ธ๋ผ์ฐ์ ์ฉ) -->
alt="์์ฒ ๋์ ํ๋กํ ์ฌ์ง" <!-- ์ ๊ทผ์ฑ + SEO ํต์ฌ -->
width="400" <!-- ๋ช
์ ํ์: Layout Shift ๋ฐฉ์ง -->
height="400" <!-- ๋ช
์ ํ์: Layout Shift ๋ฐฉ์ง -->
loading="lazy" <!-- ๋ทฐํฌํธ ๋ฐ ์ด๋ฏธ์ง ์ง์ฐ ๋ก๋ -->
/>alt ์์ฑ ์์ฑ ๊ท์น:
| ์ํฉ | alt ์์ฑ๋ฒ |
|---|---|
| ์๋ฏธ ์๋ ์ด๋ฏธ์ง | ์ด๋ฏธ์ง ๋ด์ฉ์ ๊ฐ๊ฒฐํ๊ฒ ์ค๋ช
(alt="์์ฒ ๋์ ํ๋กํ ์ฌ์ง") |
| ์ฅ์์ฉ ์ด๋ฏธ์ง | ๋น ๋ฌธ์์ด (alt="") โ ์คํฌ๋ฆฐ๋ฆฌ๋๊ฐ ๊ฑด๋๋ |
| ์์ด์ฝ + ํ ์คํธ ํจ๊ป | alt="" โ ์ ํ
์คํธ๊ฐ ์ค๋ช
ํ๋ฏ๋ก ์ค๋ณต ๋ฐฉ์ง |
| ์ฐจํธ/๊ทธ๋ํ | ๋ฐ์ดํฐ ์์ฝ ์ค๋ช
(alt="2024๋
Q4 ๋งค์ถ: 1.2์ต") |
width + height ๋ฅผ ๋ช
์ํ๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ์ด๋ฏธ์ง ๋ก๋ ์ ๊ณต๊ฐ์ ๋ฏธ๋ฆฌ ํ๋ณดํด Cumulative Layout Shift(CLS) ๋ฅผ ๋ฐฉ์งํด. LCP/CLS๋ Core Web Vitals ์งํ์ด์ SEO ๋ญํน ์์์ผ.
๐ 2. srcset + sizes โ ํด์๋ ์ ํ ๋ฌธ์ ํด๊ฒฐ
๋ชจ๋ฐ์ผ ๊ธฐ๊ธฐ์ 4000px ์ด๋ฏธ์ง๋ฅผ ๋ณด๋ด๋ ๊ฑด 5MB๋ฅผ ๋ฒ๋ฆฌ๋ ๊ฒ๊ณผ ๊ฐ์. srcset ์ผ๋ก ๋ธ๋ผ์ฐ์ ์๊ฒ ์ฌ๋ฌ ํด์๋ ์ด๋ฏธ์ง๋ฅผ ์ ๊ณตํ๊ณ ์์์ ์ ํํ๊ฒ ํด.
<!-- srcset + sizes: ๋ทฐํฌํธ ๋๋น์ ๋ฐ๋ผ ์ ์ ํ ์ด๋ฏธ์ง ์ ํ -->
<img
srcset="
images/hero-480w.jpg 480w,
images/hero-800w.jpg 800w,
images/hero-1200w.jpg 1200w
"
sizes="
(max-width: 600px) 480px,
(max-width: 1024px) 800px,
1200px
"
src="images/hero-1200w.jpg"
alt="์์๋ค ์ปค๋ฎค๋ํฐ ํ์ด๋ก ์ด๋ฏธ์ง"
width="1200"
height="600"
/>๋ธ๋ผ์ฐ์ ์ ์ด๋ฏธ์ง ์ ํ ๊ณผ์ :
- ํ์ฌ ๋ทฐํฌํธ ๋๋น ํ์ธ (์:
480px) sizes์์ ์ผ์นํ๋ ์กฐ๊ฑด ์ฐพ๊ธฐ โ480px์ฌ๋กฏsrcset์์480w์ด๋ฏธ์ง ์ ํ โhero-480w.jpg๋ก๋
๐จ 3. picture ์์ โ ์ํธ ๋๋ ์ ๋ฌธ์ ํด๊ฒฐ
๋ชจ๋ฐ์ผ์์๋ ์ธ๋ก ๋น์จ์ ํด๋ก์ฆ์
, ๋ฐ์คํฌํ์์๋ ์์ด๋ ํ๊ฒฝ ์ฌ์ง์ฒ๋ผ ๋ค๋ฅธ ๊ตฌ๋์ ์ด๋ฏธ์ง๊ฐ ํ์ํ ๋ <picture> ๋ฅผ ์จ.
<!-- picture: ๋ฏธ๋์ด ์กฐ๊ฑด์ ๋ฐ๋ผ ์์ ํ ๋ค๋ฅธ ์ด๋ฏธ์ง ์ ๊ณต -->
<picture>
<!-- ๋ชจ๋ฐ์ผ: ์ธ๋กํ, ์ธ๋ฌผ ํด๋ก์ฆ์
-->
<source
media="(max-width: 599px)"
srcset="images/team-portrait-480w.jpg"
/>
<!-- ํ๋ธ๋ฆฟ: ์ค๊ฐ ํฌ๊ธฐ -->
<source
media="(max-width: 1023px)"
srcset="images/team-medium-800w.jpg"
/>
<!-- ๋ฐ์คํฌํ: ๊ฐ๋กํ, ํ ์ ์ฒด ์ฌ์ง -->
<!-- ํด๋ฐฑ: srcset/picture ๋ฏธ์ง์ ์ ์ด img ํ์ -->
<img
src="images/team-wide-1200w.jpg"
alt="์์๋ค ์ปค๋ฎค๋ํฐ ํ ์ฌ์ง"
width="1200"
height="500"
/>
</picture>WebP ํฌ๋งท ์ง์ + ํด๋ฐฑ ํจํด:
<picture>
<!-- WebP ์ง์ ๋ธ๋ผ์ฐ์ ๋ WebP ์ฌ์ฉ (ํฌ๊ธฐ 30~50% ์ ์ฝ) -->
<source type="image/webp" srcset="image.webp" />
<!-- WebP ๋ฏธ์ง์ ๋ธ๋ผ์ฐ์ ํด๋ฐฑ -->
<img src="image.jpg" alt="์ด๋ฏธ์ง ์ค๋ช
" width="800" height="600" />
</picture>โณ 4. lazy loading โ ๋ทฐํฌํธ ๋ฐ ์ด๋ฏธ์ง ์ง์ฐ ๋ก๋
<!-- loading="lazy": ๋ทฐํฌํธ์ ๊ฐ๊น์์ง ๋ ๋ก๋ ์์ -->
<img src="thumbnail.jpg" alt="๊ฒ์๊ธ ์ธ๋ค์ผ" loading="lazy" width="300" height="200" />
<!-- loading="eager" (๊ธฐ๋ณธ๊ฐ): ์ฆ์ ๋ก๋ โ LCP ์ด๋ฏธ์ง์ ์ฌ์ฉ -->
<img src="hero.jpg" alt="ํ์ด๋ก ์ด๋ฏธ์ง" loading="eager" />์ธ์ lazy ์ฐ๊ณ , ์ธ์ eager ์ฐ๋๊ฐ?
| ์์น | ๊ถ์ฅ | ์ด์ |
|---|---|---|
| Above the fold (์ฒซ ํ๋ฉด) ์ด๋ฏธ์ง, ํ์ด๋ก | eager ๋๋ ์๋ต | ์ฆ์ ๋ณด์ฌ์ผ LCP ์ ์ ์ข์ |
| ์คํฌ๋กค ์๋ ์ฝํ ์ธ ์ด๋ฏธ์ง | lazy | ๋ถํ์ํ ์ด๊ธฐ ๋ก๋ ๋ฐฉ์ง |
| ๋ฌดํ ์คํฌ๋กค ๊ฒ์๊ธ ์ธ๋ค์ผ | lazy | ์ด๊ธฐ ๋ก๋ ์๊ฐ ํฌ๊ฒ ๋จ์ถ |
โ๏ธ 5. Next.js Image ์ปดํฌ๋ํธ
๐ฃ ์์ฒ : "Next.js ์ฐ๋ฉด <Image> ์ฐ๋ผ๋๋ฐ, ์ผ๋ฐ <img> ๋ ๋ญ๊ฐ ๋ค๋ฅธ ๊ฑฐ์์?"
import Image from "next/image";
// โ
Next.js Image: ์๋ ์ต์ ํ ์ ์ฉ
<Image
src="/images/profile.jpg"
alt="์์ฒ ๋์ ํ๋กํ ์ฌ์ง"
width={400}
height={400}
// priority: ํ์ด๋ก ์ด๋ฏธ์ง ๋ฑ LCP ๋์์ ์ฌ์ฉ (lazy=false์ ๋์ผ)
priority={false}
/>
// ์ธ๋ถ ์ด๋ฏธ์ง URL (next.config.js์ ๋๋ฉ์ธ ํ์ฉ ํ์)
<Image
src="https://cdn.example.com/image.jpg"
alt="์ธ๋ถ ์ด๋ฏธ์ง"
fill // ๋ถ๋ชจ ์ปจํ
์ด๋๋ฅผ ๊ฝ ์ฑ์
style={{ objectFit: "cover" }}
/>Next.js <Image> ๊ฐ ์๋์ผ๋ก ํด์ฃผ๋ ๊ฒ:
| ๊ธฐ๋ฅ | ์ค๋ช |
|---|---|
| ์๋ WebP/AVIF ๋ณํ | ๋ธ๋ผ์ฐ์ ์ง์์ ๋ฐ๋ผ ์ต์ ํฌ๋งท์ผ๋ก ๋ณํ |
| ์๋ srcset ์์ฑ | ๊ธฐ๊ธฐ ํด์๋์ ๋ฐ๋ฅธ srcset์ ์๋ ์์ฑ |
| ๋ ์ด์์ ์ํํธ ๋ฐฉ์ง | width/height ๊ธฐ๋ฐ์ผ๋ก ๊ณต๊ฐ ์์ฝ |
| lazy loading ๊ธฐ๋ณธ ์ ์ฉ | ๋ทฐํฌํธ ๋ฐ ์ด๋ฏธ์ง ์๋ ์ง์ฐ ๋ก๋ |
| ์ด๋ฏธ์ง ์บ์ฑ | Next.js ์๋ฒ์์ ์ต์ ํ๋ ์ด๋ฏธ์ง ์บ์ฑ |
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. ๋ค์ ์ค ์ด๊ธฐ ํ์ด์ง ๋ก๋ ์ฑ๋ฅ์ ์ํด ํ์ด๋ก ์ด๋ฏธ์ง์ ์ ์ฉํด์ผ ํ ์ฌ๋ฐ๋ฅธ img ์์ฑ ์กฐํฉ์?
- A)
loading="lazy"+decoding="async" - B)
loading="eager"+width+height๋ช ์ - ๊ฐ)
loading="lazy"+srcset๋ง ์ฌ์ฉ - ๋ผ)
alt=""+loading="lazy"
โ ์ ๋ต: B
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: ํ์ด๋ก ์ด๋ฏธ์ง๋ ์ฒซ ํ๋ฉด์ ๋ฐ๋ก ๋ณด์ฌ์ผ ํ๋ LCP ๋์ ์ด๋ฏธ์ง์์.
loading="lazy"๋ฅผ ์ฐ๋ฉด ์คํ๋ ค ๋ก๋๋ฅผ ๋ฏธ๋ค LCP ์ ์๊ฐ ๋๋น ์ ธ์.loading="eager"(๋๋ ์๋ต) ๋ก ์ฆ์ ๋ก๋ํ๊ณ ,width/height๋ฅผ ๋ช ์ํด ๋ ์ด์์ ์ํํธ(CLS)๋ฅผ ๋ฐฉ์งํด์ผ ํด์. - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "ํ์ด๋ก ์ด๋ฏธ์ง =
eager(๋๋ ์๋ต) + width/height ๋ช ์, ์คํฌ๋กค ์๋ ์ด๋ฏธ์ง =lazy"
Q2. Next.js์์ ์ธ๋ถ CDN์ ์ด๋ฏธ์ง๋ฅผ <Image> ์ปดํฌ๋ํธ๋ก ์ฌ์ฉํ๋ ค๋ฉด ์ด๋ค ์ค์ ์ด ํ์ํ๊ฐ?
โ
์ ๋ต: next.config.js ์ images.remotePatterns (๋๋ images.domains) ์ ํ์ฉํ ๋๋ฉ์ธ์ ๋ฑ๋ก
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: Next.js
<Image>์ ์ต์ ํ ๊ธฐ๋ฅ์ด ์ธ๋ถ ์ด๋ฏธ์ง์๋ ์ ์ฉ๋๋ ค๋ฉด ๋ณด์์ ์ํด ํ์ฉ ๋๋ฉ์ธ์ ๋ช ์ํด์ผ ํด์. ๋ฑ๋กํ์ง ์์ผ๋ฉด ๋น๋/๋ฐํ์ ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค. - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "์ธ๋ถ ์ด๋ฏธ์ง =
next.config.js๋๋ฉ์ธ ํ์ฉ ํ์"
Q3. ์์ฒ ์ด์ ํ ์คํธ ํ์
์์๋ค ์ปค๋ฎค๋ํฐ ๋ฉ์ธ ํ์ด์ง์ ํ ์๊ฐ ์น์ ์ด ์๋ค.
๋ฐ์คํฌํ์์๋ ํ ์ ์ฒด๋ฅผ ๋ด์ ์์ด๋ ์ฌ์ง, ๋ชจ๋ฐ์ผ์์๋ ๋ํ ์ผ๊ตด ํด๋ก์ฆ์ ์ผ๋ก ๋ณด์ฌ์ค์ผ ํ๋ค.
์ด๋ค HTML ์์๋ฅผ ์ฐ๋ฉด ์ด๊ฑธ ๊ตฌํํ ์ ์๋๊ฐ?
โ
์ ๋ต: <picture> ์์ + <source media="..."> ์กฐํฉ
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: "๋ทฐํฌํธ ํฌ๊ธฐ์ ๋ฐ๋ผ ์์ ํ ๋ค๋ฅธ ๊ตฌ๋/๊ตฌ์ฑ์ ์ด๋ฏธ์ง๋ฅผ ๋ณด์ฌ์ค๋ค" ๋ ๊ฒ ์ํธ ๋๋ ์
๋ฌธ์ ์์.
<picture>๋media์์ฑ์ผ๋ก ์กฐ๊ฑด์ ๊ฑธ์ด ๊ฐ๊ธฐ ๋ค๋ฅธ ์ด๋ฏธ์ง๋ฅผ ์ ๊ณตํ ์ ์์ด์.srcset+sizes๋ ๊ฐ์ ์ด๋ฏธ์ง์ ๋ค๋ฅธ ํด์๋๋ง ์ ํํ๋ฏ๋ก ์ด ๊ฒฝ์ฐ์<picture>๊ฐ ๋ง์์. - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "์ด๋ฏธ์ง ๊ตฌ๋๊ฐ ๋ค๋ฆ =
<picture>์ํธ ๋๋ ์ , ๊ฐ์ ์ด๋ฏธ์ง ๋ค๋ฅธ ํฌ๊ธฐ =srcset + sizes"
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋ Lighthouse ๋๋ ธ๋๋ "Properly size images" ๊ฒฝ๊ณ ๊ฐ 50๊ฐ ๋๊ฒ ๋์๋ค. ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋ฅ ๋๋ ค๋ฐ์์๋๋ฐ ์ด๊ฒ ์ผ๋ง๋ ๋ญ๋น์ธ์ง ์ด์ ์ผ ์ค๊ฐํ๋ค. srcset ์ ์ฉํ๊ณ ๋์ ๋ชจ๋ฐ์ผ Lighthouse ์ ์๊ฐ 42์ ์์ 76์ ์ผ๋ก ์ฌ๋๋ค. 34์ ์ด ์ด๋ฏธ์ง ์ต์ ํ ํ๋๋ก ์ฌ๋ผ๊ฐ ๊ฑฐ๋ค.
๐ก "์ด๋ฏธ์ง๋ ์น ์ฑ๋ฅ์ ๊ฐ์ฅ ํฐ ๋ณ์๋ค.
srcset์ผ๋ก ํด์๋ ์ ํ,picture๋ก ์ํธ ๋๋ ์ ,loading='lazy'๋ก ์ด๊ธฐ ๋ก๋ ์ต์ํ โ ์ด ์ธ ๊ฐ์ง๊ฐ ์ด๋ฏธ์ง ์ต์ ํ์ ํต์ฌ์ด๋ค."
Next.js <Image> ๊ฐ ์ด ๋ชจ๋ ๊ฑธ ์๋์ผ๋ก ํด์ค๋ค๋ ๊ฒ ์ง์ง ์ฒ์์๋ "๊ทธ๋ฅ img ์ฐ๋ฉด ๋์ง ์ Image๋ฅผ ์จ์ผ ํด?" ํ๋๋ฐ, ์ด ๋ฌธ์ ์ฐ๊ณ ๋์ ์ด์ ์ดํด๋๋ค. ์์ผ๋ก <img> ์ง์ ์ธ ๋ ์ด ์ฒดํฌ๋ฆฌ์คํธ ํ ๋ฒ์ฉ ๋๋ ค๋ด์ผ๊ฒ ๋ค.
๐ ๋ ์์๋ณด๊ธฐ