sharp 이미지 최적화 — 명령 하나로 웹 이미지 압축 자동화
웹사이트가 느리다면 이미지부터 의심해야 합니다. Photoshop을 열 필요 없이, sharp 라이브러리 하나면 터미널에서 이미지 최적화를 자동으로 처리할 수 있습니다. WebP/AVIF 변환, 리사이즈, 배치 처리까지 명령어 하나로 끝내는 방법을 정리했습니다.
~90%
파일 크기 감소
200장
30초 배치 처리
WebP
주력 포맷
AVIF
차세대 포맷
1sharp 라이브러리 소개
sharp는 Node.js 환경의 고성능 이미지 처리 라이브러리입니다. 내부적으로 libvips를 사용하기 때문에 ImageMagick보다 4~5배 빠릅니다. Next.js의 이미지 최적화에도 sharp가 사용됩니다.
# 설치
npm install sharp # 또는 yarn add sharp pnpm add sharp
sharp의 주요 특징
2WebP vs AVIF 비교
두 포맷 모두 JPEG보다 압축률이 좋지만, 특성이 다릅니다. 어떤 포맷을 선택할지 비교해봤습니다.
| 항목 | WebP | AVIF | JPEG |
|---|---|---|---|
| 압축률 (동일 품질) | 25~35% 절감 | 40~50% 절감 | 기준 |
| 인코딩 속도 | 빠름 | 느림 (5~10배) | 매우 빠름 |
| 브라우저 지원 | 97%+ | 92%+ | 100% |
| 투명도 지원 | 지원 | 지원 | 미지원 |
| 애니메이션 | 지원 | 지원 | 미지원 |
| 권장 용도 | 범용 (현시점 최적) | 정적 이미지 고압축 | 폴백용 |
현실적 선택
WebP를 메인으로 쓰고, AVIF는 점진적으로 도입하는 것을 권장합니다. AVIF가 압축률은 더 좋지만, 인코딩 시간이 오래 걸리고 아직 Safari 구버전에서 지원이 불완전합니다. 빌드 시간이 중요한 프로젝트라면 WebP만으로도 충분합니다.
3압축 스크립트 작성
기본적인 이미지 변환 스크립트부터 시작합니다. 하나의 이미지를 WebP와 AVIF 두 포맷으로 동시에 변환하는 코드입니다.
# optimize.mjs - 기본 변환 스크립트
import sharp from 'sharp';
import path from 'path';
async function optimizeImage(inputPath) {
const dir = path.dirname(inputPath);
const name = path.basename(inputPath, path.extname(inputPath));
// WebP 변환 (품질 80, 리사이즈 1920px)
await sharp(inputPath)
.resize({ width: 1920, withoutEnlargement: true })
.webp({ quality: 80 })
.toFile(path.join(dir, `${name}.webp`));
// AVIF 변환 (품질 65, 더 높은 압축률)
await sharp(inputPath)
.resize({ width: 1920, withoutEnlargement: true })
.avif({ quality: 65 })
.toFile(path.join(dir, `${name}.avif`));
console.log(`완료: ${name}`);
}
// 사용: node optimize.mjs ./images/photo.jpg
optimizeImage(process.argv[2]);# 실행 결과 예시
$ node optimize.mjs ./public/images/hero.jpg 원본: hero.jpg 2.4 MB WebP: hero.webp 320 KB (87% 감소) AVIF: hero.avif 210 KB (91% 감소)
withoutEnlargement 옵션은 필수입니다
원본보다 작은 이미지를 리사이즈할 때 강제로 키우는 걸 방지합니다. 800px짜리 아이콘을 1920px로 늘리면 용량만 커지고 품질은 떨어지니까요.
4배치 처리 자동화
이미지가 한두 장이면 수동으로 해도 되지만, 수십~수백 장이면 자동화가 필수입니다. 폴더 안의 모든 이미지를 한번에 처리하는 스크립트입니다.
# batch-optimize.mjs - 폴더 전체 배치 처리
import sharp from 'sharp';
import fs from 'fs';
import path from 'path';
const SUPPORTED = ['.jpg', '.jpeg', '.png', '.tiff'];
async function batchOptimize(inputDir, outputDir) {
const files = fs.readdirSync(inputDir)
.filter(f => SUPPORTED.includes(
path.extname(f).toLowerCase()
));
console.log(`${files.length}개 파일 처리 시작...`);
fs.mkdirSync(outputDir, { recursive: true });
let totalSaved = 0;
for (const file of files) {
const inputPath = path.join(inputDir, file);
const name = path.basename(file, path.extname(file));
const originalSize = fs.statSync(inputPath).size;
await sharp(inputPath)
.resize({ width: 1920, withoutEnlargement: true })
.webp({ quality: 80 })
.toFile(path.join(outputDir, `${name}.webp`));
const newSize = fs.statSync(
path.join(outputDir, `${name}.webp`)
).size;
const saved = originalSize - newSize;
totalSaved += saved;
console.log(
` ${file} → ${name}.webp `
+ `(${(saved/1024).toFixed(0)}KB 절감)`
);
}
console.log(
`\n총 ${(totalSaved/1024/1024).toFixed(1)}MB 절감`
);
}
batchOptimize('./public/images', './public/images/optimized');처리 속도
200장/30초
1920px WebP 기준
평균 압축률
85~90%
JPEG → WebP 기준
메모리 사용
~100MB
순차 처리 시
5품질 튜닝 가이드
이미지 용도에 따라 최적의 품질 설정이 다릅니다. 무조건 낮추면 용량은 줄지만 화질이 깨지고, 너무 높으면 압축 효과가 없습니다.
| 이미지 용도 | WebP 품질 | AVIF 품질 | 최대 너비 |
|---|---|---|---|
| 히어로 배경 | 85 | 70 | 1920px |
| 블로그 썸네일 | 80 | 65 | 800px |
| 카드 이미지 | 75 | 60 | 600px |
| 아이콘/로고 | 90 | 80 | 원본 유지 |
| 갤러리/포트폴리오 | 85 | 70 | 1440px |
| OG 이미지 | 80 | 65 | 1200px |
# 용도별 설정 적용 예시
const profiles = {
hero: { width: 1920, webpQ: 85, avifQ: 70 },
thumbnail: { width: 800, webpQ: 80, avifQ: 65 },
card: { width: 600, webpQ: 75, avifQ: 60 },
icon: { width: null, webpQ: 90, avifQ: 80 },
};
async function optimize(input, profile = 'thumbnail') {
const { width, webpQ, avifQ } = profiles[profile];
const pipeline = sharp(input);
if (width) pipeline.resize({ width, withoutEnlargement: true });
await pipeline.clone().webp({ quality: webpQ })
.toFile(input.replace(/\.[^.]+$/, '.webp'));
await pipeline.clone().avif({ quality: avifQ })
.toFile(input.replace(/\.[^.]+$/, '.avif'));
}튜닝 팁
품질 70 이하로 내리면 텍스트가 포함된 이미지에서 뭉개짐이 눈에 띄기 시작합니다. 사진 위주라면 60까지도 괜찮지만, 스크린샷이나 다이어그램은 80 이상을 유지하세요.
6빌드 파이프라인 통합
수동으로 스크립트를 돌리는 건 결국 까먹게 됩니다. 빌드 과정에 넣어서 자동으로 실행되게 만드는 게 좋습니다.
# package.json에 스크립트 추가
{
"scripts": {
"optimize:images": "node scripts/batch-optimize.mjs",
"prebuild": "npm run optimize:images",
"build": "next build"
}
}통합 시 고려사항
Next.js와 함께 사용할 때
Next.js의 <Image> 컴포넌트는 자체적으로 이미지 최적화를 하지만, 빌드 타임에 미리 최적화해두면 런타임 부하를 줄이고 초기 로딩 속도를 개선할 수 있습니다. 특히 정적 생성(SSG) 사이트에서 효과가 큽니다.
이 글의 핵심 정리
- ✓sharp는 Node.js 최고 성능의 이미지 처리 라이브러리 — ImageMagick보다 4~5배 빠름
- ✓WebP를 메인으로, AVIF는 점진 도입 권장 — 브라우저 호환성과 인코딩 속도 고려
- ✓배치 처리로 200장 이미지를 30초에 처리 가능 — 수동 작업 대비 압도적 효율
- ✓용도별 품질 튜닝 필수 — 히어로 85, 썸네일 80, 카드 75가 기본 설정
- ✓빌드 파이프라인에 통합하면 최적화를 까먹을 일이 없음
함께 읽으면 좋은 글
본 글은 2026년 2월 기준 sharp 0.33.x 버전을 기반으로 작성되었습니다. 라이브러리 버전에 따라 API가 달라질 수 있으며, 브라우저 포맷 지원률은 지속적으로 변합니다. 이미지 품질 설정은 이미지 특성에 따라 달라지므로, 실제 적용 전 시각적 품질을 확인하세요. 본 콘텐츠의 비상업적 공유는 자유이나, 상업적 이용 시 문의 페이지를 통해 연락 바랍니다.
댓글
(4개)로그인하면 댓글을 작성할 수 있습니다.
WebP vs AVIF 비교표가 깔끔합니다. 아직 Safari 구버전 호환성 때문에 WebP를 메인으로 쓰고 AVIF는 점진 도입 중입니다.
빌드 파이프라인에 통합하는 방법이 실용적입니다. CI/CD에서 이미지 최적화를 자동으로 돌리면 배포 사이즈가 확 줄어듭니다.
블로그 이미지가 30장 넘어가니까 수동 최적화는 불가능했는데, 배치 처리 스크립트 덕분에 한방에 해결했습니다.
관련 글
© 2026 TreeRU. All rights reserved.
본 콘텐츠의 저작권은 TreeRU에 있으며, 출처를 밝히지 않은 무단 전재 및 재배포를 금합니다. 인용 시 출처(treeru.com)를 반드시 명시해 주세요.