WebP가 JPEG보다 커질 때: 사진 종류별 WebP·AVIF 실측
인쇄기 롤러 견적 사이트를 만들면서 화면에 나가는 사진을 전부 WebP로 정했습니다. 제품 사진 네 장으로 재 보고 내린 결론이었습니다. 그 뒤에 설계도 사진 세 장이 들어왔고, 같은 스크립트에 그대로 태웠습니다. 이 글을 쓰려고 다시 재 보니 그중 두 장은 WebP가 JPEG보다 컸습니다.
원본은 assets, 화면에 나가는 파일은 public
원본 사진은 assets/에 커밋하고, 화면에 나가는 파일은 빌드 때 public/에 만듭니다. public/은 커밋하지 않습니다. 둘 다 커밋해 두면 원본을 바꾸고 굽기를 잊은 날 두 벌이 조용히 갈라집니다.
변환은 sharp로 짠 스크립트 하나가 합니다. npm run dev와 npm run build 앞에 predev·prebuild로 물려 있고, 결과 파일이 원본보다 새것이면 건너뜁니다. dev 서버를 켤 때마다 다시 굽지 않게 하려는 것입니다.
const JOBS = [
{ from: "assets/rollers", to: "public/rollers", ext: ".webp",
encode: (img) => img.resize({ width: 768, withoutEnlargement: true }).webp({ quality: 75 }) },
{ from: "assets/brand", to: "public/brand", ext: ".png",
encode: (img) => img.png({ palette: true, colors: 64, compressionLevel: 9 }) },
{ from: "assets/blueprints", to: "public/blueprints", ext: ".webp",
encode: (img) => img.rotate().resize({ width: 1600, withoutEnlargement: true }).webp({ quality: 80 }) },
];Next.js의 next/image는 쓰지 않았습니다. next/image는 요청이 들어올 때 서버가 크기와 형식을 바꿔 캐시해 두는 방식입니다. 이 사이트의 사진은 요청마다 달라질 것이 없어서, 빌드 때 한 번 줄여 두면 런타임에 할 일이 남지 않습니다.
롤러 사진 네 장에서 WebP를 고른 근거
제품 카드의 폭이 380px 안팎이라 사진은 폭 768px로 줄였습니다. 2배 밀도 화면에서도 선명한 크기입니다. 네 장을 형식별로 구우면 이렇습니다.
| 형식 (설정) | 네 장 합계 | 인코딩 시간 (네 장) |
|---|---|---|
| 원본 JPEG | 164.2KB | |
| JPEG (mozjpeg q75) | 52.4KB | |
| WebP q75 | 31.6KB | 61ms |
| AVIF q50 | 20.3KB | 225ms |
AVIF가 가장 작았습니다. 그래도 네 장을 합쳐 11KB 차이였고 인코딩은 3.7배 걸렸습니다. 카탈로그 첫 화면의 이미지 전송량이 로고까지 합쳐 37.2KB라, 11KB를 더 줄이자고 형식을 올릴 이유를 찾지 못했습니다. 브라우저 지원도 WebP가 AVIF보다 몇 해 앞서 있습니다. WebP로 정했습니다.
카드의 <img>에는 width={768} height={512}를 적어 두었습니다. 사진이 늦게 도착해도 브라우저가 자리를 먼저 잡아 두니 카드 아래가 밀리지 않습니다. 화면 밖의 카드는 loading="lazy"로 스크롤할 때 받습니다.
글자 테두리 때문에 PNG로 남긴 로고
로고는 159×35 래스터 한 장뿐입니다. 손실 압축에서는 작은 글자의 테두리가 뭉개졌습니다. 그래서 로고만 무손실 PNG로 두고 색을 64개로 줄였습니다(팔레트 PNG). 16.3KB가 5.6KB가 됐습니다. 색이 몇 개 안 되는 그림은 색 수를 줄여도 눈에 띄는 것이 없고, 줄인 만큼 무손실 압축이 잘 먹힙니다.
레티나 화면에서는 조금 흐립니다. 원본이 그 크기뿐이라 벡터 원본을 받으면 SVG로 바꿀 생각입니다.
설계도 사진에서 뒤집힌 WebP와 JPEG의 순서
설계도는 거래처가 처음 건넨 자료 세 장입니다. 한 장은 종이에 손으로 그린 옵션 스케치이고, 두 장은 엑셀 규격표가 떠 있는 모니터를 휴대폰으로 찍은 사진입니다. 글씨가 읽혀야 해서 폭을 1600px로 넉넉히 두고 WebP q80으로 구웠습니다. 롤러 사진에서 WebP가 이겼으니 여기서도 그럴 거라 여겼고, 따로 재지 않았습니다.
그림: 세 장 합계. 원본(4,846.4KB)은 막대가 너무 길어 뺐습니다.
| 사진 | 원본 | JPEG (mozjpeg q80) | WebP q80 | AVIF q50 |
|---|---|---|---|---|
| 손그림 스케치 (세로) | 428.3KB | 119.6KB | 65.4KB | 46.3KB |
| 모니터 속 엑셀 표 ① | 2,330.3KB | 379.4KB | 459.4KB | 165.0KB |
| 모니터 속 엑셀 표 ② | 2,087.8KB | 353.8KB | 424.4KB | 146.0KB |
| 합계 | 4,846.4KB | 852.9KB | 949.2KB | 357.4KB |
손그림은 롤러 사진과 같은 순서가 나왔습니다. 모니터를 찍은 두 장은 WebP가 JPEG보다 각각 80KB, 71KB 컸습니다. 세 장을 합치면 WebP를 쓰는 바람에 JPEG로 냈을 때보다 96KB를 더 보내고 있었습니다. AVIF와의 차이는 11KB가 아니라 592KB였습니다.
품질 숫자는 인코더마다 척도가 달라서 같은 숫자가 같은 화질을 뜻하지 않습니다. 위 표는 각 설정에서 나온 크기이고 화질을 맞춘 비교가 아닙니다. 그래서 화질은 눈으로 따로 봤습니다.
WebP가 용량을 쓴 곳, 모니터 잔무늬
엑셀 표 사진의 같은 조각을 세 형식으로 잘라 1:1로 나란히 놓았습니다. 숫자와 한글은 셋 다 또렷이 읽혔습니다. 차이는 배경에 있었습니다. 모니터를 찍은 사진에는 화면 픽셀이 만든 잔무늬(모아레)가 깔려 있는데, AVIF는 이것을 조금 뭉개 평평하게 만들었고 WebP와 JPEG는 그대로 살렸습니다.
WebP가 여기서 JPEG에 진 이유가 이 무늬라고 봅니다. 읽어야 할 정보와 상관없는 잔무늬를 충실히 옮기느라 용량을 썼다는 뜻입니다. 확인한 것은 아닙니다. 인코더 설정을 바꿔 가며 원인을 좁혀 보지는 않았습니다.
빌드는 초록불인데 이미지가 404인 구조
이미지를 빌드 때 굽는 방식에는 함정이 하나 따라옵니다. Docker 이미지를 npx next build로 지으면 npm의 prebuild 훅이 돌지 않아서 public/에 사진이 생기지 않습니다. 그리고 Next.js의 standalone 산출물은 public/을 가져가지 않아서 러너 단계로 따로 복사해야 합니다. 둘 중 하나만 빠져도 빌드는 성공하고 배포도 초록불인데 사진과 로고가 전부 404입니다.
그래서 Dockerfile의 해당 줄마다 왜 npm run build인지, 왜 public/을 복사하는지 주석을 붙여 두었습니다. 누가 npx next build로 줄이려는 순간 그 주석을 먼저 읽게 됩니다.
다시 골라야 하는 설계도 사진의 형식
후보는 둘입니다. 설계도만 AVIF로 옮기거나, 사진마다 형식을 달리해 모니터 사진은 JPEG로 내는 것입니다. 저는 AVIF 쪽으로 기울어 있습니다. 설계도는 빌드 때 한 번 굽고 끝이라 세 장에 1.4초 걸리는 인코딩은 비용이라 할 것이 못 되고, 592KB는 11KB와 달리 휴대폰 회선에서 체감되는 크기입니다.
설계도 페이지의 <img>에는 width와 height도 빠져 있습니다. 롤러 카드에는 적어 두고 여기서는 잊었습니다. 사진이 늦게 오면 그 아래 내용이 한 번 밀려 내려갑니다.
형식을 한 번 정하면 그 뒤에 들어오는 사진에도 그 결론이 조용히 따라붙습니다. 결론을 낸 표본이 제품 사진 네 장이었다는 사실은 설정 파일에 남지 않았습니다.
우리 서비스에 적용할 계획이 있으신가요?
홈페이지·웹서비스 개발 살펴보기 →함께 읽기
- Expo 앱은 만들었는데 출시가 안 됩니다: `eas build`보다 먼저 준비할 것들Expo를 사용하면 네이티브 빌드 과정이 크게 단순해집니다. 하지만 앱스토어 출시까지 명령어 한두 줄로 끝나는 것은 아닙니다. 실제 자동 배포를 만들려면 앱 등록, 서명 자격 증명, 스토어 API 권한, CI/CD 승인 절차가 먼저 준비되어야 합니다.
- 27B보다 2.4B가 더 나았던 문서 RAG 모델 선택기문서 검색과 질의응답을 결합한 RAG 시스템을 만들 때 가장 먼저 떠오르는 질문은 대개 비슷합니다.
- MoE 입문: 36B 모델이 27B보다 6배 빨랐던 이유로컬에서 AI 모델을 돌리다 보면 이런 상식이 생깁니다. 모델이 크면 똑똑하지만 느리다. 대체로 맞습니다.
- Vercel 커스텀 도메인에 HTTPS 인증서가 안 붙을 때: 도메인을 DNS보다 먼저 추가한 경우고객사 업무 시스템을 고객사 도메인의 서브도메인(shop.고객사도메인)에 붙이는 일이었습니다. 기존 홈페이지를 맡은 호스팅 업체에 CNAME 한 줄을 부탁했고, 이틀 뒤 「넣었다」는 답이 왔습니다. 그런데 주소를 열면 연결이 안 된다는 연락이 이어서 왔습니다.
- Next.js와 Postgres 조합이 작은 팀에 좋은 이유웹 서비스를 만들 때 기술 스택은 단순한 취향 문제가 아닙니다. 개발 속도, 유지보수, 확장성, 채용 가능성, 운영 비용까지 영향을 줍니다. 작은 팀이나 1인 개발 조직이라면 특히 “적은 인원으로 오래 가져갈 수 있는 조합”이 중요합니다.