๐Ÿš€ Next.js 14์žฅ: next/image & next/font โ€” Core Web Vitals๋ฅผ ์ง€ํ‚ค๋Š” ์ตœ์ ํ™” ๋ฌด๊ธฐ

๐Ÿ“‹ ๊ฐœ์š”

next/image์™€ next/font๋กœ Core Web Vitals๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ์ด๋ฏธ์ง€ยทํฐํŠธ ์ตœ์ ํ™” ๊ธฐ๋ฒ•์„ ์•Œ์•„๋ด…๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 13๋ถ„ (์ „์ฒด) / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 6๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

  • ์˜์ฒ (์‹ ์ž…): "Lighthouse ๋Œ๋ ค๋ดค๋”๋‹ˆ Performance ์ ์ˆ˜๊ฐ€ 47์ ์ด์—์š”. LCP๊ฐ€ 5์ดˆ๊ฐ€ ๋„˜๊ณ  CLS๋„ ๋นจ๊ฐ„๋ถˆ์ด์—์š”. ์ด๋ฏธ์ง€ ์ตœ์ ํ™”๋ฅผ ์–ด๋””์„œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์•ผ ํ• ์ง€ ๋ชจ๋ฅด๊ฒ ์–ด์š”. <img> ํƒœ๊ทธ๋ฅผ <Image>๋กœ ๋ฐ”๊พธ๋ฉด ์ž๋™์œผ๋กœ ๋‹ค ํ•ด๊ฒฐ๋˜๋‚˜์š”?"
  • ์˜ํ˜ธ(๋ฆฌ๋“œ): "์˜์ฒ  ๋‹˜, <img> โ†’ <Image> ๊ต์ฒด๋Š” ์‹œ์ž‘์ ์ด์ง€ ๋์ด ์•„๋‹ˆ์—์š”. priority, sizes, fill ์†์„ฑ์„ ์ œ๋Œ€๋กœ ์จ์•ผ LCP๊ฐ€ ์žกํ˜€์š”. ๊ทธ๋ฆฌ๊ณ  Google Fonts๋ฅผ <link> ํƒœ๊ทธ๋กœ ์—ฐ๊ฒฐํ•˜๋ฉด ์™ธ๋ถ€ ๋„คํŠธ์›Œํฌ ์™•๋ณต์ด ๋ฐœ์ƒํ•ด์„œ ํฐํŠธ ๊นœ๋นก์ž„(FOUT)์ด ์ƒ๊ฒจ์š”. next/font๋กœ ๋ฐ”๊พธ๋ฉด ์™„์ „ํžˆ ์‚ฌ๋ผ์ง€๋Š”๋ฐ, ์ด๊ฒŒ Lighthouse ์ ์ˆ˜๋ฅผ 10์  ์ด์ƒ ์˜ฌ๋ ค์ฃผ๋Š” ๊ฟ€ํŒ์ด์—์š”."

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„
<img> ํƒœ๊ทธ์˜ ๋ฌธ์ œ โ†’ next/image ๊ธฐ๋ณธ โ†’ LCP ์ตœ์ ํ™” ์†์„ฑ โ†’ next/font๋กœ ํฐํŠธ ๊นœ๋นก์ž„ ์ œ๊ฑฐ โ†’ ๊ณ ๊ธ‰ ํŒจํ„ด

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • next/image์˜ priority, sizes, fill ์†์„ฑ์„ ์–ธ์ œ ์–ด๋–ป๊ฒŒ ์จ์•ผ ํ•˜๋Š”์ง€ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค
  • next/font๋กœ Google Fonts๋ฅผ ์—ฐ๊ฒฐํ•ด FOIT/FOUT์„ ์™„์ „ํžˆ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค
  • Lighthouse LCP ์ ์ˆ˜๊ฐ€ ๋‚ฎ์„ ๋•Œ ์ฒดํฌํ•  ํฌ์ธํŠธ๋ฅผ ์•Œ๊ณ  ์žˆ๋‹ค

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€

์ผ๋ฐ˜ <img> ํƒœ๊ทธ๋ฅผ ๊ทธ๋Œ€๋กœ ์“ฐ๋ฉด ์–ด๋–ค ์ผ์ด ์ƒ๊ธฐ๋Š”์ง€ ์•Œ์•„?

  1. ์›๋ณธ ์ด๋ฏธ์ง€ ๊ทธ๋Œ€๋กœ ์ „์†ก โ€” 5MB 4K ์‚ฌ์ง„์„ ๋ชจ๋ฐ”์ผ 300px ํ™”๋ฉด์— ๊ทธ๋Œ€๋กœ ๋‚ด๋ ค๋ณด๋‚ด
  2. Layout Shift โ€” ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ๋ชจ๋ฅด๋‹ˆ ๋กœ๋“œ๋˜๋ฉด์„œ ์•„๋ž˜ ๋‚ด์šฉ์ด ์‘ฅ ๋ฐ€๋ ค (CLS ์•…ํ™”)
  3. ์ง€์—ฐ ๋กœ๋“œ ์•ˆ ๋จ โ€” ์Šคํฌ๋กคํ•˜๋ฉด ๋ณด์ผ ์ด๋ฏธ์ง€๋„ ์ฆ‰์‹œ ๋‹ค์šด๋กœ๋“œ ์‹œ์ž‘ (LCP ์•…ํ™”)
  4. WebP ๋ณ€ํ™˜ ์—†์Œ โ€” JPEG ๊ทธ๋Œ€๋กœ ์ „์†ก, ์ตœ์‹  ํ˜•์‹๋ณด๋‹ค 40% ์šฉ๋Ÿ‰ ๋‚ญ๋น„

๊ตฌ๊ธ€์˜ Core Web Vitals ์—ฐ๊ตฌ์— ๋”ฐ๋ฅด๋ฉด ํžˆ์–ด๋กœ ์ด๋ฏธ์ง€ ํ•˜๋‚˜๋ฅผ ์ตœ์ ํ™”ํ•˜์ง€ ์•Š์œผ๋ฉด LCP๊ฐ€ 2~5์ดˆ ์•…ํ™”๋˜๊ณ , ์ด๊ฒŒ ์ดํƒˆ๋ฅ  35% ์ฆ๊ฐ€๋กœ ์ด์–ด์ง„๋‹ค๊ณ  ํ•ด.

next/image๋Š” ์ด ๋ชจ๋“  ๊ฑธ ์ž๋™์œผ๋กœ ํ•ด๊ฒฐํ•ด์ค˜. ๋ฌธ์ œ๋Š” "๊ทธ๋ƒฅ ์“ด๋‹ค"๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ "์ œ๋Œ€๋กœ ์จ์•ผ ํ•œ๋‹ค"๋Š” ๊ฑฐ์•ผ.


๐Ÿ—๏ธ ๋น„์œ ๋กœ ๋จผ์ € ์ดํ•ดํ•˜๊ธฐ

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด?
์Œ์‹ ๋ฐฐ๋‹ฌ์„ ์‹œ์ผฐ๋Š”๋ฐ ํ•ญ์ƒ ๊ฐ€์žฅ ํฐ ํฌ์žฅ ๋ฐ•์Šค๋กœ๋งŒ ๋ฐฐ๋‹ฌํ•ด์ฃผ๋Š” ๊ฐ€๊ฒŒ๊ฐ€ ์žˆ์–ด. ์ปต๋ผ๋ฉด ํ•˜๋‚˜๋ฅผ ์‹œ์ผœ๋„ ์ด์‚ฟ์ง ๋ฐ•์Šค์— ๋„ฃ์–ด์„œ ์™€. ๋‚ญ๋น„์ž–์•„?

next/image๋Š” "์˜๋ฆฌํ•œ ํฌ์žฅ๊ธฐ"์•ผ. ํฐ ํ™”๋ฉด์—” ํฐ ์ด๋ฏธ์ง€, ์ž‘์€ ํ™”๋ฉด์—” ์ž‘์€ ์ด๋ฏธ์ง€๋ฅผ ๋”ฑ ๋งž๊ฒŒ ์ž˜๋ผ์„œ ๋ณด๋‚ด์ค˜. ๊ทธ๋ฆฌ๊ณ  WebP๋ผ๋Š” ๋” ๊ฐ€๋ฒผ์šด ํฌ์žฅ ๋ฐฉ์‹์œผ๋กœ ์ž๋™ ๋ณ€ํ™˜ํ•ด์ค˜.

next/font์˜ ๋น„์œ :

์Œ์‹์  ๋ฉ”๋‰ดํŒ์ด ์ฒ˜์Œ์—” ๋นˆ ์ข…์ด์˜€๋‹ค๊ฐ€, ์ž ์‹œ ํ›„์— ๊ธ€์”จ๊ฐ€ ํŽœ์œผ๋กœ ์“ฐ์—ฌ์ง€๋Š” ๊ฑธ ์ƒ์ƒํ•ด๋ด. ๊ทธ ์ˆœ๊ฐ„ ์†๋‹˜๋“ค์ด ํ™”๋“ค์ง ๋†€๋ผ์ง€? ์ด๊ฒŒ FOUT(ํฐํŠธ ๊นœ๋นก์ž„)์ด์•ผ.

next/font๋Š” ๋ฉ”๋‰ดํŒ์„ ๊ฐ€์ ธ์˜ฌ ๋•Œ๋ถ€ํ„ฐ ์ด๋ฏธ ๊ธ€์”จ๊ฐ€ ์ ํ˜€์žˆ๊ฒŒ ํ•ด์ค˜. ํฐํŠธ๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์ „๊นŒ์ง€ ์‹œ์Šคํ…œ ํฐํŠธ๋ฅผ ์ •ํ™•ํ•œ ํฌ๊ธฐ๋กœ ์˜ˆ์•ฝํ•ด๋‘๋Š” ๊ฑฐ์•ผ.


๐Ÿ–ผ๏ธ next/image โ€” ์ด๋ฏธ์ง€ ์ตœ์ ํ™” ์ž๋™ํ™” ๊ธฐ๊ณ„ ๐ŸŸข

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • next/image์˜ <Image> ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ธฐ๋ณธ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค
  • width, height ๋˜๋Š” fill ์†์„ฑ์ด ์™œ ํ•„์ˆ˜์ธ์ง€ ์ดํ•ดํ•œ๋‹ค
// โŒ ์˜์ฒ ์ด์˜ ์ˆœ์ง„ํ•œ ์ฝ”๋“œ โ€” ์ผ๋ฐ˜ img ํƒœ๊ทธ
<img src="/hero.jpg" alt="์Šคํ„ฐ๋”” ๋ชจ์ง‘ ๋ฐฐ๋„ˆ" />
// ๋ฌธ์ œ: ์›๋ณธ ํฌ๊ธฐ ๊ทธ๋Œ€๋กœ ์ „์†ก, CLS ๋ฐœ์ƒ, WebP ๋ณ€ํ™˜ ์—†์Œ
 
// โœ… ์˜ํ˜ธ๊ฐ€ ๊ณ ์ณ์ค€ ์ฝ”๋“œ โ€” next/image
import Image from 'next/image'
 
// ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์•Œ ๋•Œ (์ •์  ์ด๋ฏธ์ง€, ์•Œ๋ ค์ง„ ํฌ๊ธฐ)
<Image
  src="/hero.jpg"
  alt="์Šคํ„ฐ๋”” ๋ชจ์ง‘ ๋ฐฐ๋„ˆ"
  width={1200}    // ๋ฐ˜๋“œ์‹œ ํ•„์š” โ€” CLS ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ๋ฏธ๋ฆฌ ๊ณต๊ฐ„ ํ™•๋ณด
  height={630}
/>

next/image๊ฐ€ ์ž๋™์œผ๋กœ ํ•ด์ฃผ๋Š” ๊ฒƒ:

๊ธฐ๋Šฅ์„ค๋ช…
WebP/AVIF ์ž๋™ ๋ณ€ํ™˜๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ง€์›ํ•˜๋ฉด ๋” ๊ฐ€๋ฒผ์šด ํ˜•์‹์œผ๋กœ ์ž๋™ ๋ณ€ํ™˜
๋ฐ˜์‘ํ˜• ํฌ๊ธฐ ์กฐ์ ˆ์š”์ฒญํ•œ ํฌ๊ธฐ์— ๋งž๊ฒŒ ์„œ๋ฒ„์—์„œ ๋ฆฌ์‚ฌ์ด์ง•
Lazy Loading๋ทฐํฌํŠธ์— ๋“ค์–ด์˜ฌ ๋•Œ ๋กœ๋“œ (๊ธฐ๋ณธ๊ฐ’)
CLS ๋ฐฉ์ง€width/height๋กœ ๋ ˆ์ด์•„์›ƒ ๊ณต๊ฐ„ ๋ฏธ๋ฆฌ ํ™•๋ณด
์บ์‹ฑCDN ๋˜๋Š” Next.js ์„œ๋ฒ„์—์„œ ์ตœ์ ํ™”๋œ ์ด๋ฏธ์ง€ ์บ์‹ฑ

โš ๏ธ ์ฃผ์˜: <Image>๋Š” alt ์†์„ฑ์ด ํ•„์ˆ˜์•ผ. ์›น ์ ‘๊ทผ์„ฑ(Accessibility) ๋•Œ๋ฌธ์ด๊ธฐ๋„ ํ•˜์ง€๋งŒ, ์ด๋ฏธ์ง€๊ฐ€ ๋กœ๋“œ ์•ˆ ๋์„ ๋•Œ ๋Œ€์ฒด ํ…์ŠคํŠธ๋กœ ํ‘œ์‹œ๋ผ. ๋นˆ ๋ฌธ์ž์—ด alt=""์€ "์žฅ์‹์šฉ ์ด๋ฏธ์ง€"๋ผ๋Š” ์˜๋ฏธ์•ผ.

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
next/image์—์„œ width/height๋Š” "์‹ค์ œ ํฌ๊ธฐ"๊ฐ€ ์•„๋‹ˆ๋ผ "๋น„์œจ ํžŒํŠธ"์•ผ. ์‹ค์ œ ํ‘œ์‹œ ํฌ๊ธฐ๋Š” CSS๋กœ ์ œ์–ดํ•˜๊ณ , ์—ฌ๊ธฐ์„  CLS ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ์˜ˆ์•ฝ๋œ ๊ณต๊ฐ„ ๋น„์œจ๋งŒ ์„ ์–ธํ•˜๋Š” ๊ฑฐ์•ผ.


๐Ÿ”ค next/font โ€” ํฐํŠธ ๊นœ๋นก์ž„(FOIT/FOUT) ๋ฐ•๋ฉธ ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • Google Fonts๋ฅผ next/font/google๋กœ ์—ฐ๊ฒฐํ•ด FOIT/FOUT์„ ์™„์ „ํžˆ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค
  • ํฐํŠธ๋ฅผ CSS ๋ณ€์ˆ˜๋กœ ๋“ฑ๋กํ•ด ์ „์—ญ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํŒจํ„ด์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค

๐Ÿ“– ์šฉ์–ด: FOIT/FOUT

  • FOIT (Flash Of Invisible Text) โ€” ํฐํŠธ ๋กœ๋“œ ์ „ ํ…์ŠคํŠธ๊ฐ€ ์•ˆ ๋ณด์ด๋‹ค๊ฐ€ ๊ฐ‘์ž๊ธฐ ๋‚˜ํƒ€๋‚จ
  • FOUT (Flash Of Unstyled Text) โ€” ํฐํŠธ ๋กœ๋“œ ์ „ ๊ธฐ๋ณธ ํฐํŠธ๋กœ ํ‘œ์‹œ๋˜๋‹ค๊ฐ€ ๊ฐ‘์ž๊ธฐ ๋ฐ”๋€œ
// โŒ ์˜์ฒ ์ด์˜ ์ˆœ์ง„ํ•œ ๋ฐฉ๋ฒ• โ€” HTML์— Google Fonts ๋งํฌ ํƒœ๊ทธ
// app/layout.tsx
<head>
  {/* ์ด๊ฑด ์™ธ๋ถ€ ์„œ๋ฒ„ ์™•๋ณต ๋ฐœ์ƒ โ†’ ๋А๋ฆฌ๊ณ , FOUT ์ƒ๊น€ */}
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR" />
</head>
 
// โœ… ์˜ํ˜ธ๊ฐ€ ๊ถŒ์žฅํ•˜๋Š” ๋ฐฉ๋ฒ• โ€” next/font/google
// app/layout.tsx
import { Noto_Sans_KR, Inter } from 'next/font/google'
 
const notoSansKR = Noto_Sans_KR({
  subsets: ['latin'],          // ๋ผํ‹ด ๋ฌธ์ž ์„œ๋ธŒ์…‹ (ํ•œ๊ธ€์€ ์ž๋™ ํฌํ•จ)
  weight: ['400', '700'],      // ์‚ฌ์šฉํ•  ๊ตต๊ธฐ ์ง€์ • (๋ถˆํ•„์š”ํ•œ ๊ตต๊ธฐ ๋กœ๋“œ ๋ฐฉ์ง€)
  variable: '--font-noto',     // CSS ๋ณ€์ˆ˜๋ช…์œผ๋กœ ๋“ฑ๋ก
  display: 'swap',             // FOIT ๋Œ€์‹  FOUT ํ—ˆ์šฉ (๊ธฐ๋ณธ๊ฐ’, ๊ถŒ์žฅ)
})
 
const inter = Inter({
  subsets: ['latin'],
  variable: '--font-inter',
})
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    // className์œผ๋กœ ํฐํŠธ CSS ๋ณ€์ˆ˜๋ฅผ <html> ํƒœ๊ทธ์— ๋“ฑ๋ก
    <html lang="ko" className={`${notoSansKR.variable} ${inter.variable}`}>
      <body>{children}</body>
    </html>
  )
}
/* globals.css โ€” CSS ๋ณ€์ˆ˜๋กœ ํฐํŠธ ์‚ฌ์šฉ */
body {
  font-family: var(--font-noto), var(--font-inter), sans-serif;
}

next/font์˜ ์žฅ์ :

๊ธฐ์กด ๋ฐฉ์‹ (link ํƒœ๊ทธ)next/font
์™ธ๋ถ€ ์„œ๋ฒ„ ์™•๋ณต ๋ฐœ์ƒํฐํŠธ ํŒŒ์ผ ๋กœ์ปฌ ๋‹ค์šด๋กœ๋“œ ํ›„ ์ž์ฒด ์„œ๋น™
FOUT ๋ฐœ์ƒsize-adjust๋กœ ๋ ˆ์ด์•„์›ƒ ์ด๋™ ๋ฐฉ์ง€
๊ฐœ์ธ์ •๋ณด (IP) Google์— ์ „์†กํ”„๋ผ์ด๋ฒ„์‹œ ๋ณดํ˜ธ (์™ธ๋ถ€ ์š”์ฒญ ์—†์Œ)
๋งค ํŽ˜์ด์ง€ ๋ Œ๋”๋งˆ๋‹ค blockingํฐํŠธ preload๋กœ ๋น ๋ฅธ ํ‘œ์‹œ

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
next/font๋Š” Google Fonts๋ฅผ ์šฐ๋ฆฌ ์„œ๋ฒ„์—์„œ ์ง์ ‘ ์„œ๋น™ํ•˜๋Š” ์ž์ฒด CDN์„ ๋งŒ๋“œ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์•„. ์™ธ๋ถ€ ์š”์ฒญ ์—†๊ณ , ๊นœ๋นก์ž„ ์—†๊ณ , ๋น ๋ฅด๊ณ .


โšก ํ•ต์‹ฌ ์„ฑ๋Šฅ ์ง€ํ‘œ: LCP๋ฅผ ์žก์•„๋ผ ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • priority ์†์„ฑ์œผ๋กœ LCP ์ด๋ฏธ์ง€๋ฅผ ์„ ์–ธํ•ด preload ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค
  • sizes ์†์„ฑ์œผ๋กœ ๋ฐ˜์‘ํ˜• ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ๋ธŒ๋ผ์šฐ์ €์— ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ๋‹ค

LCP(Largest Contentful Paint) โ€” ๋ทฐํฌํŠธ์—์„œ ๊ฐ€์žฅ ํฐ ์š”์†Œ๊ฐ€ ๋ Œ๋”๋ง๋˜๋Š” ์‹œ๊ฐ„. ๋Œ€๋ถ€๋ถ„ ํžˆ์–ด๋กœ ์ด๋ฏธ์ง€๊ฐ€ LCP ์š”์†Œ์•ผ.

๐Ÿค” ์ž ๊น, ๋จผ์ € ์ƒ๊ฐํ•ด๋ด
๊ธฐ๋ณธ์ ์œผ๋กœ <Image>๋Š” lazy loading์ด์•ผ. ๊ทธ๋Ÿฐ๋ฐ ํŽ˜์ด์ง€ ์ตœ์ƒ๋‹จ์˜ ํžˆ์–ด๋กœ ์ด๋ฏธ์ง€๋„ lazy loading์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

// โŒ ํžˆ์–ด๋กœ ์ด๋ฏธ์ง€์— lazy loading์ด ์ ์šฉ๋œ ์ƒํƒœ (๊ธฐ๋ณธ๊ฐ’)
<Image
  src="/hero.jpg"
  alt="ํžˆ์–ด๋กœ ๋ฐฐ๋„ˆ"
  width={1200}
  height={630}
  // priority ์—†์œผ๋ฉด lazy loading โ†’ LCP ์ด๋ฏธ์ง€์ธ๋ฐ ๋Šฆ๊ฒŒ ๋กœ๋“œ๋จ!
/>
 
// โœ… ํžˆ์–ด๋กœ ์ด๋ฏธ์ง€์—” priority ์ถ”๊ฐ€
<Image
  src="/hero.jpg"
  alt="ํžˆ์–ด๋กœ ๋ฐฐ๋„ˆ"
  width={1200}
  height={630}
  priority        // โ†’ preload ์ฒ˜๋ฆฌ, ์ฆ‰์‹œ ๋‹ค์šด๋กœ๋“œ ์‹œ์ž‘
  // โ† LCP ์ด๋ฏธ์ง€์—๋งŒ ์“ธ ๊ฒƒ. ๋ชจ๋“  ์ด๋ฏธ์ง€์— ์“ฐ๋ฉด ์˜คํžˆ๋ ค ์—ญํšจ๊ณผ
/>

sizes ์†์„ฑ โ€” ๋ฐ˜์‘ํ˜• ์ด๋ฏธ์ง€์˜ ํ•ต์‹ฌ:

// โŒ sizes ์—†๋Š” ๊ฒฝ์šฐ
// ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ๋ชจ๋ฅด๋‹ˆ ํ•ญ์ƒ ๋ทฐํฌํŠธ 100% ํฌ๊ธฐ์˜ ์ด๋ฏธ์ง€๋ฅผ ์š”์ฒญ
// ๋ชจ๋ฐ”์ผ(375px)์ธ๋ฐ ๋ฐ์Šคํฌํ†ฑ์šฉ 1200px ์ด๋ฏธ์ง€๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๋‚ญ๋น„ ๋ฐœ์ƒ!
<Image src="/card.jpg" alt="์Šคํ„ฐ๋”” ์นด๋“œ" width={400} height={300} />
 
// โœ… sizes ์†์„ฑ์œผ๋กœ ๋ฐ˜์‘ํ˜• ํžŒํŠธ ์ œ๊ณต
<Image
  src="/card.jpg"
  alt="์Šคํ„ฐ๋”” ์นด๋“œ"
  width={400}
  height={300}
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 400px"
  // ์„ค๋ช…: ๋ชจ๋ฐ”์ผ(768px ์ดํ•˜)์—์„  ํ™”๋ฉด ์ „์ฒด, ํƒœ๋ธ”๋ฆฟ(1200px ์ดํ•˜)์—์„  ์ ˆ๋ฐ˜, ๊ทธ ์™ธ์—” 400px
/>

๐ŸŽ›๏ธ ๊ณ ๊ธ‰ ์ด๋ฏธ์ง€ ํŒจํ„ด: fill, sizes, blurDataURL ๐Ÿ”ด

๐Ÿ“ fill ๋ชจ๋“œ โ€” ๋ถ€๋ชจ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๊ฝ‰ ์ฑ„์šฐ๊ธฐ

์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ๋ฏธ๋ฆฌ ์•Œ ์ˆ˜ ์—†๊ฑฐ๋‚˜, ์ปจํ…Œ์ด๋„ˆ์— ๋งž์ถฐ ๊ฝ‰ ์ฑ„์›Œ์•ผ ํ•  ๋•Œ ์จ.

// ์นด๋“œ ์ธ๋„ค์ผ, ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ๋“ฑ์— ํ™œ์šฉ
<div style={{ position: 'relative', width: '100%', height: '200px' }}>
  {/* fill ์‚ฌ์šฉ ์‹œ ๋ถ€๋ชจ๋Š” ๋ฐ˜๋“œ์‹œ position: relative */}
  <Image
    src={post.thumbnail}
    alt={post.title}
    fill
    style={{ objectFit: 'cover' }}   // ๋น„์œจ ์œ ์ง€ํ•˜๋ฉฐ ์ฑ„์šฐ๊ธฐ
    sizes="(max-width: 768px) 100vw, 50vw"
  />
</div>

๐ŸŒซ๏ธ blurDataURL โ€” ์ด๋ฏธ์ง€ ๋กœ๋“œ ์ „ ๋ธ”๋Ÿฌ ํ”Œ๋ ˆ์ด์Šคํ™€๋”

// ์™ธ๋ถ€ ์ด๋ฏธ์ง€์— ๋ธ”๋Ÿฌ ํšจ๊ณผ ํ”Œ๋ ˆ์ด์Šคํ™€๋”
<Image
  src={post.thumbnail}
  alt={post.title}
  width={400}
  height={300}
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..."
  // base64 ์ธ์ฝ”๋”ฉ๋œ ์ดˆ์ €ํ•ด์ƒ๋„ ์ด๋ฏธ์ง€ (1x1ํ”ฝ์…€ ์ •๋„๋ฉด ์ถฉ๋ถ„)
/>

๐ŸŒ ์™ธ๋ถ€ ์ด๋ฏธ์ง€ ๋„๋ฉ”์ธ ํ—ˆ์šฉ (next.config.ts)

์™ธ๋ถ€ URL ์ด๋ฏธ์ง€๋ฅผ next/image๋กœ ์ตœ์ ํ™”ํ•˜๋ ค๋ฉด ๋„๋ฉ”์ธ์„ ํ—ˆ์šฉํ•ด์•ผ ํ•ด.

// next.config.ts
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.youngsu.community',   // ์šฐ๋ฆฌ CDN
        pathname: '/uploads/**',
      },
      {
        protocol: 'https',
        hostname: '**.githubusercontent.com',  // GitHub ์•„๋ฐ”ํƒ€
      },
    ],
  },
}

๐Ÿ’ฅ ์—๋Ÿฌ ํ•ด๊ฒฐ ์นดํƒˆ๋กœ๊ทธ


โŒ Invalid src prop โ€” ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ์™ธ๋ถ€ ๋„๋ฉ”์ธ

์–ธ์ œ ๋‚˜์˜ค๋Š”๊ฐ€?

Error: Invalid src prop on `next/image`, hostname "example.com" is not configured under `images` in your `next.config.js`

ํ•ด๊ฒฐ์ฑ…:

// next.config.ts์— ํ•ด๋‹น ๋„๋ฉ”์ธ ์ถ”๊ฐ€
images: {
  remotePatterns: [{ protocol: 'https', hostname: 'example.com' }],
}

โŒ Image with src ... missing required width/height

์›์ธ: <Image>์— width, height ๋˜๋Š” fill ์—†์ด ์‚ฌ์šฉ.

ํ•ด๊ฒฐ์ฑ…: ํฌ๊ธฐ๋ฅผ ์•Œ๋ฉด width/height ์ถ”๊ฐ€. ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ฑ„์›Œ์•ผ ํ•˜๋ฉด fill ์‚ฌ์šฉ.


โŒ ํฐํŠธ๊ฐ€ ๋งค ๋ฐฐํฌ๋งˆ๋‹ค ๋‹ค๋ฅธ URL ์ƒ์„ฑ โ†’ ์บ์‹œ ๋ฌดํšจํ™”

์›์ธ: next/font๋Š” ๋นŒ๋“œ๋งˆ๋‹ค ํฐํŠธ URL์— ํ•ด์‹œ๋ฅผ ์ถ”๊ฐ€ํ•ด.

ํ•ด๊ฒฐ์ฑ…: ์‹ค์ œ๋กœ๋Š” ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ์•ผ. Next.js๊ฐ€ ์ž๋™์œผ๋กœ ์ ์ ˆํ•œ ์บ์‹œ ํ—ค๋”๋ฅผ ์„ค์ •ํ•ด์ค˜. ๋ธŒ๋ผ์šฐ์ €๋„ ์ƒˆ URL๋กœ ์ƒˆ๋กœ ๋ฐ›์•„๊ฐ€์ง€๋งŒ, CDN ์บ์‹ฑ์œผ๋กœ ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌ๋ผ.


๐Ÿ ์ด๋ฒˆ์— ๋ฐฐ์šด ๋‚ด์šฉ ์ด์ •๋ฆฌ

๐Ÿ“‹ ์ƒํ™ฉ๋ณ„ ์ด๋ฏธ์ง€ ํŒจํ„ด

์ƒํ™ฉ์†์„ฑ ์กฐํ•ฉ
ํžˆ์–ด๋กœ ์ด๋ฏธ์ง€ (LCP)width + height + priority + sizes
์นด๋“œ ์ธ๋„ค์ผ (ํฌ๊ธฐ ์œ ๋™)fill + objectFit: cover + sizes
์ •์  ์ด๋ฏธ์ง€ (ํฌ๊ธฐ ๊ณ ์ •)width + height
๋ธ”๋Ÿฌ ํ”Œ๋ ˆ์ด์Šคํ™€๋”placeholder="blur" + blurDataURL

โš ๏ธ ์ ˆ๋Œ€ ํ•˜์ง€ ๋ง ๊ฒƒ

์ƒํ™ฉโŒ ๋‚˜์œ ์˜ˆโœ… ์ข‹์€ ์˜ˆ
๋ชจ๋“  ์ด๋ฏธ์ง€์— priority๋ชจ๋‘ ์ฆ‰์‹œ ๋กœ๋“œ โ†’ ์ดˆ๊ธฐ ๋กœ๋“œ ๋А๋ ค์งํžˆ์–ด๋กœ(LCP) ์ด๋ฏธ์ง€์—๋งŒ
sizes ์—†์ด fill ์‚ฌ์šฉ๋ทฐํฌํŠธ 100% ์ด๋ฏธ์ง€ ํ•ญ์ƒ ์š”์ฒญsizes ๋ฐ˜๋“œ์‹œ ๊ฐ™์ด
Google Fonts link ํƒœ๊ทธ์™ธ๋ถ€ ์™•๋ณต ๋ฐœ์ƒ, FOUTnext/font/google ์‚ฌ์šฉ

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. ๋‹ค์Œ ์ค‘ priority ์†์„ฑ์„ ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€ํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์€?

  • A) ํŽ˜์ด์ง€ ํ•˜๋‹จ์˜ ๊ด€๋ จ ๊ฒŒ์‹œ๊ธ€ ์ธ๋„ค์ผ ์ด๋ฏธ์ง€
  • B) ๋ทฐํฌํŠธ ์ตœ์ƒ๋‹จ์— ๋…ธ์ถœ๋˜๋Š” ํžˆ์–ด๋กœ ๋ฐฐ๋„ˆ ์ด๋ฏธ์ง€
  • C) ์Šคํฌ๋กคํ•ด์•ผ ๋ณด์ด๋Š” ์‚ฌ์šฉ์ž ์•„๋ฐ”ํƒ€ ์ด๋ฏธ์ง€
  • D) ํด๋ฆญํ•˜๋ฉด ์—ด๋ฆฌ๋Š” ๋ชจ๋‹ฌ ์•ˆ์˜ ์ด๋ฏธ์ง€

โœ… ์ •๋‹ต: B

ํ•ด์„ค: priority๋Š” preload ์ฒ˜๋ฆฌ๋ผ ์ฆ‰์‹œ ๋‹ค์šด๋กœ๋“œ๊ฐ€ ์‹œ์ž‘๋ผ. LCP ์š”์†Œ์ธ ํžˆ์–ด๋กœ ์ด๋ฏธ์ง€์—๋งŒ ์จ์•ผ ํšจ๊ณผ๊ฐ€ ์žˆ๊ณ , ๋‚˜๋จธ์ง€์— ๋‚จ์šฉํ•˜๋ฉด ์ดˆ๊ธฐ ๋กœ๋“œ ์„ฑ๋Šฅ์ด ์˜คํžˆ๋ ค ๋‚˜๋น ์ ธ.

๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "๋ทฐํฌํŠธ์— ์ฒ˜์Œ๋ถ€ํ„ฐ ๋ณด์ด๋Š” ์ด๋ฏธ์ง€ ์ค‘ ์ œ์ผ ํฐ ๊ฒƒ ํ•˜๋‚˜์—๋งŒ priority."


Q2. Google Fonts๋ฅผ next/font๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•  ๋•Œ์˜ ๊ฐ€์žฅ ํฐ ์ด์ ์ด ์•„๋‹Œ ๊ฒƒ์€?

  • A) ์™ธ๋ถ€ ์„œ๋ฒ„ ์™•๋ณต์ด ์‚ฌ๋ผ์ ธ ํฐํŠธ ๋กœ๋“œ๊ฐ€ ๋นจ๋ผ์ง„๋‹ค
  • B) FOUT/FOIT์ด ์ œ๊ฑฐ๋œ๋‹ค
  • C) ์‚ฌ์šฉ์ž IP๊ฐ€ Google์— ์ „์†ก๋˜์ง€ ์•Š์•„ ํ”„๋ผ์ด๋ฒ„์‹œ๊ฐ€ ๋ณดํ˜ธ๋œ๋‹ค
  • D) ํฐํŠธ ํŒŒ์ผ ์šฉ๋Ÿ‰์ด ์ž๋™์œผ๋กœ 50% ๊ฐ์†Œํ•œ๋‹ค

โœ… ์ •๋‹ต: D

ํ•ด์„ค: ํฐํŠธ ํŒŒ์ผ ์ž์ฒด์˜ ์šฉ๋Ÿ‰์€ next/font๊ฐ€ ์ค„์—ฌ์ฃผ์ง€ ์•Š์•„. ๋‹ค๋งŒ subsets์™€ weight ์˜ต์…˜์œผ๋กœ ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ์–ด.

๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: A, B, C๋Š” ์ง„์งœ ์ด์ . D๋Š” ์˜คํ•ด. "์šฉ๋Ÿ‰ ๊ฐ์†Œ"๊ฐ€ ์•„๋‹ˆ๋ผ "๋ถˆํ•„์š”ํ•œ ์„œ๋ธŒ์…‹ ์ œ์™ธ"๊ฐ€ ์ •ํ™•ํ•œ ํ‘œํ˜„์ด์•ผ.


Q3. ์นœ๊ตฌ์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด?

sizes ์†์„ฑ ์—†์ด fill์„ ์“ฐ๋ฉด ์™œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋Š”์ง€ ๋น„์œ ๋กœ ์„ค๋ช…ํ•ด๋ด.

์˜ˆ์‹œ ๋‹ต๋ณ€:

"ํƒ๋ฐฐ ๋ฐ•์Šค ํฌ๊ธฐ๋ฅผ ๋ง ์•ˆ ํ•˜๋ฉด ๋ฐฐ๋‹ฌ ๊ธฐ์‚ฌ๊ฐ€ ์ œ์ผ ํฐ ๋ฐ•์Šค๋กœ ๋ณด๋‚ด๋ฒ„๋ ค. sizes ์—†์ด fill์„ ์“ฐ๋ฉด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ '์–ผ๋งˆ๋‚˜ ํฐ ์ด๋ฏธ์ง€๊ฐ€ ํ•„์š”ํ•œ์ง€ ๋ชจ๋ฅด๋‹ˆ๊นŒ ์ œ์ผ ํฐ ๊ฑธ๋กœ ์ฃผ์„ธ์š”'๋ผ๊ณ  ์š”์ฒญํ•ด. ๋ชจ๋ฐ”์ผ 375px ํ™”๋ฉด์ธ๋ฐ 1920px ์ด๋ฏธ์ง€๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๋‚ญ๋น„๊ฐ€ ์ƒ๊ธฐ๋Š” ๊ฑฐ์•ผ."


๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์˜ค๋Š˜์€ ์šฐ๋ฆฌ ์„œ๋น„์Šค์˜ '์„ฑ๋Šฅ๊ณผ ๋ฏธํ•™'์„ ๋™์‹œ์— ์žก์€ ๋‚ ์ด์•ผ! ๊ทธ๋™์•ˆ ๊ณ ํ™”์งˆ ์ด๋ฏธ์ง€๋ฅผ ์˜ฌ๋ฆด ๋•Œ๋งˆ๋‹ค ์‚ฌ์ดํŠธ๊ฐ€ ๋ฒ„๋ฒ…๊ฑฐ๋ ค์„œ ๊ณ ๋ฏผ์ด ๋งŽ์•˜๋Š”๋ฐ, ์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์ด ์•Œ๋ ค์ฃผ์‹  'next/image' ๋•๋ถ„์— 5MB ์‚ฌ์ง„๋„ 100KB๋Œ€๋กœ ๊ฐ€๋ณ๊ฒŒ ์„œ๋น™ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋์–ด.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "์ด๋ฏธ์ง€๋Š” next/image๋กœ ๊ฐ€๋ณ๊ฒŒ, ํฐํŠธ๋Š” next/font๋กœ ํ”๋“ค๋ฆผ ์—†์ด! ์„ฑ๋Šฅ์ด ๊ณง ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด๋‹ค."

ํŠนํžˆ ํฐํŠธ๊ฐ€ ๊นœ๋นก๊ฑฐ๋ฆฌ๋Š” ๋ฌธ์ œ(FOUT)๋ฅผ next/font๋กœ ํ•ด๊ฒฐํ–ˆ์„ ๋•Œ, ์‚ฌ์ดํŠธ๊ฐ€ ํ›จ์”ฌ ๊ณ ๊ธ‰์Šค๋Ÿฌ์›Œ์ง„ ๊ฑธ ๋А๋ผ๋ฉด์„œ ์ •๋ง ๋ฟŒ๋“ฏํ–ˆ์–ด. ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ๋‹จ์ˆœํžˆ ์ˆซ์ž๋ฅผ ๋‚ฎ์ถ”๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์‚ฌ์šฉ์ž์—๊ฒŒ ์‹ ๋ขฐ๋ฅผ ์ฃผ๋Š” ๊ณผ์ •์ด๋ผ๋Š” ๊ฑธ ๊นจ๋‹ฌ์•˜์–ด. ์˜ค๋Š˜ ๋ฟŒ๋“ฏํ•œ ๋งˆ์Œ์œผ๋กœ ํ‡ด๊ทผํ•ด์„œ ๋ณด๊ณ  ์‹ถ์—ˆ๋˜ ๋„ทํ”Œ๋ฆญ์Šค ์ •์ฃผํ–‰์ด๋‚˜ ํ•ด์•ผ์ง€. ๋‚ด์ผ์€ ๋” '๊ฐ๊ฐ์ ์ธ' ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜๋Š” ๊ฑฐ์•ผ! ๐Ÿฃ


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ