OPEN HYPER STEP
← 목록으로 (stack-analysis)
STACK-ANALYSIS · 92 / 120
stack-analysis
CHAPTER 92 / 120
읽기 약 2
FUNCTION

이미지 최적화: WebP/AVIF + lazy + srcset


핵심 개념

next/image·sharp·responsive images·lazy loading — 이미지 90% 용량 감소.

본문

포맷 비교

📋 코드 (10줄)
| 포맷 | 압축률 | 호환성 | 적합 |
|---|---|---|---|
| JPEG | 기준 (100%) | 100% | 사진 (폴백) |
| PNG | 무손실 | 100% | 투명·아이콘 |
| WebP | 30% 작음 | 97% | 일반 사진 |
| AVIF | 50% 작음 | 92% | 최신 브라우저 |
| SVG | 벡터 | 100% | 아이콘·로고 |


→ AVIF → WebP → JPEG 순으로 폴백

next/image 자동 변환

TSX📋 코드 (27줄)
import Image from 'next/image';

<Image
  src="/photo.jpg"
  alt="제품 사진"
  width={800}
  height={600}
  sizes="(max-width: 768px) 100vw, 50vw"
  quality={80}             // 기본 75
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,..."
  loading="lazy"           // 기본 (priority 외)
/>


// next.config.js
module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    minimumCacheTTL: 60 * 60 * 24 * 365,  // 1년
    remotePatterns: [
      { protocol: 'https', hostname: 'cdn.example.com' },
    ],
  },
};

srcset + sizes — 반응형

HTML📋 코드 (25줄)
<!-- 브라우저가 가장 적합한 크기 자동 선택 -->
<img
  src="/photo-800w.jpg"
  srcset="
    /photo-400w.jpg 400w,
    /photo-800w.jpg 800w,
    /photo-1600w.jpg 1600w,
    /photo-3200w.jpg 3200w
  "
  sizes="
    (max-width: 640px) 100vw,
    (max-width: 1024px) 50vw,
    33vw
  "
  alt=""
  loading="lazy"
/>


<!-- picture로 포맷 분기 -->
<picture>
  <source srcset="/photo.avif" type="image/avif" />
  <source srcset="/photo.webp" type="image/webp" />
  <img src="/photo.jpg" alt="" />
</picture>

sharp로 변환 (서버)

TYPESCRIPT📋 코드 (26줄)
import sharp from 'sharp';

// 업로드 → 다중 사이즈·포맷 자동 생성
async function processUpload(buffer: Buffer, key: string) {
  const sizes = [400, 800, 1600];
  const formats = ['avif', 'webp', 'jpeg'];

  for (const size of sizes) {
    for (const fmt of formats) {
      const output = await sharp(buffer)
        .resize(size, null, { fit: 'inside', withoutEnlargement: true })
        .toFormat(fmt as any, {
          quality: fmt === 'avif' ? 60 : fmt === 'webp' ? 75 : 80,
        })
        .toBuffer();

      await s3.send(new PutObjectCommand({
        Bucket: BUCKET,
        Key: `${key}-${size}.${fmt}`,
        Body: output,
        ContentType: `image/${fmt}`,
        CacheControl: 'public, max-age=31536000, immutable',
      }));
    }
  }
}

Lazy Loading — Native + Polyfill

HTML📋 코드 (21줄)
<!-- Native (97% 지원) -->
<img src="/photo.jpg" loading="lazy" decoding="async" alt="" />


<!-- IntersectionObserver fallback -->
<script>
if (!('loading' in HTMLImageElement.prototype)) {
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(e => {
      if (e.isIntersecting) {
        const img = e.target;
        img.src = img.dataset.src;
        observer.unobserve(img);
      }
    });
  });
  document.querySelectorAll('img[loading="lazy"]').forEach(img => {
    observer.observe(img);
  });
}
</script>

blur placeholder — UX

TYPESCRIPT📋 코드 (16줄)
// plaiceholder로 빌드 타임 생성
import { getPlaiceholder } from 'plaiceholder';
import fs from 'fs/promises';

async function generateBlur(path: string) {
  const buffer = await fs.readFile(path);
  const { base64 } = await getPlaiceholder(buffer);
  return base64;  // data:image/jpeg;base64,...
}


// 또는 Sharp로 작은 LQIP (Low Quality Image Placeholder)
async function makeLQIP(buffer: Buffer) {
  const tiny = await sharp(buffer).resize(20).webp({ quality: 30 }).toBuffer();
  return `data:image/webp;base64,${tiny.toString('base64')}`;
}

CDN 활용 — Cloudflare Images

TYPESCRIPT📋 코드 (21줄)
// 이미지 업로드 → Cloudflare Images (자동 변환·CDN)
const formData = new FormData();
formData.append('file', file);

const res = await fetch(
  `https://api.cloudflare.com/client/v4/accounts/.../images/v1`,
  {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${CF_API_TOKEN}` },
    body: formData,
  }
);


// 사용 — variant로 사이즈 지정
<img src="https://imagedelivery.net/.../photo-id/public" />
<img src="https://imagedelivery.net/.../photo-id/thumbnail" />
<img src="https://imagedelivery.net/.../photo-id/w=800" />


// 비용: $5/100K 이미지·월 + $1/100GB 전송

다음 챕터

CH.93 "번들 최적화: Tree Shaking + Code Splitting".


AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude

무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6

내 코드의 이미지 최적화 부분을 분석해서
실전 분석 + 개선 우선순위를 알려줘.
ChatGPT

무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro

이미지 최적화 관련 베스트 프랙티스 5가지를
비교 분석해서 패턴 추출를 알려줘.
Gemini

무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro

내 프로젝트 전체에서 이미지 최적화
최적화 가능 위치를 보고해줘.
Grok

무료: Grok 4.1 / SuperGrok $30/mo

2026년 한국 시장의 이미지 최적화
트렌드를 솔직히 알려줘.

⭐ 이것만 기억하세요
이미지 최적화: WebP/AVIF + lazy + srcset 이 3가지만 확실히 잡으세요
1.AVIF → WebP → JPEG 폴백 = 모든 브라우저 + 50% 용량 감소
2.srcset + sizes로 디바이스 적합 사이즈 자동 선택
3.blur placeholder + lazy loading = UX·LCP 동시 개선


공유하기
진행도 92 / 120