--- id: frontend-image-optimization title: Image Optimization — WebP / AVIF / srcset / lazy category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [frontend, image, performance, web, vibe-coding] tech_stack: { language: "TS / React / Next.js", applicable_to: ["Web"] } applied_in: [] aliases: [next/image, srcset, sizes, AVIF, lazy load, LCP, blurhash] --- # Image Optimization > 페이지 무게의 60% = 이미지. **WebP/AVIF + responsive (srcset/sizes) + lazy + LCP preload** 4종. Next.js `` / Cloudinary / imgix / @vercel/og 가 자동 처리. ## 📖 핵심 개념 - LCP (Largest Contentful Paint): 보통 hero image. 빨리 로드 = SEO + UX. - Format: AVIF (최신, 작음) → WebP → JPEG. PNG 는 투명 / 도형. - srcset: 화면 / DPR 별 이미지. - sizes: layout 기반 어떤 크기 선택할지 hint. ## 💻 코드 패턴 ### Next.js `` ```tsx import Image from 'next/image'; hero ``` ### 일반 HTML ```html hero ``` ### Cloudinary (URL 기반 변환) ```ts const url = `https://res.cloudinary.com/x/image/upload/w_800,f_auto,q_auto/hero.jpg`; // f_auto: 브라우저에 맞는 포맷 자동 // q_auto: 압축 자동 ``` ### Lazy load (native) ```html ``` ### Blurhash placeholder ```tsx import { Blurhash } from 'react-blurhash';
{!loaded && } setLoaded(true)} src={...} />
``` ### React Native — FastImage (cache + priority) ```tsx import FastImage from 'react-native-fast-image'; ``` ### Bitmap subsample (Android) ```kotlin val opts = BitmapFactory.Options().apply { inSampleSize = 2 } // 1/2 크기 val bmp = BitmapFactory.decodeFile(path, opts) ``` ### iOS — UIImage downsampling ```swift func downsample(url: URL, to size: CGSize, scale: CGFloat) -> UIImage? { let opts = [kCGImageSourceShouldCache: false] as CFDictionary guard let src = CGImageSourceCreateWithURL(url as CFURL, opts) else { return nil } let pixel = max(size.width, size.height) * scale let dsOpts = [ kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceShouldCacheImmediately: true, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceThumbnailMaxPixelSize: pixel, ] as CFDictionary guard let cg = CGImageSourceCreateThumbnailAtIndex(src, 0, dsOpts) else { return nil } return UIImage(cgImage: cg) } ``` ### LCP preload ```html ``` ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | Next.js | `` | | 정적 사이트 | `` + AVIF/WebP | | 동적 변환 / CDN | Cloudinary / imgix / Imgproxy | | LCP hero | priority + preload + placeholder | | 무한 스크롤 | lazy + blurhash | | 모바일 native | FastImage / Glide / Coil | | 사용자 업로드 | server 에서 resize + format | ## ❌ 안티패턴 - **JPEG only**: WebP/AVIF 가 30~50% 작음. - **원본 표시**: 1080p 4MB → 200kB 압축 가능. - **Width/height 누락**: layout shift. - **Lazy LCP**: 첫 paint 느려짐. priority + preload. - **Fixed sizes 큰 이미지**: srcset 필수. - **Decoding sync**: paint 블록. `decoding="async"`. - **이미지 안에 텍스트**: SEO 안 됨, 번역 안 됨. ## 🤖 LLM 활용 힌트 - AVIF/WebP + srcset + sizes + lazy(LCP 제외) + blur placeholder. - Native = FastImage / Coil / Glide + downsample. ## 🔗 관련 문서 - [[Web_Core_Web_Vitals]] - [[CDN_Caching_Strategies]] - [[Native_Memory_Profiling]]