[Part 5] 이미지가 깨져 보였던 이유 3가지
TL;DR
- 결과 이미지가 깨져 보인 원인은 세 가지였다. 이중 압축, 칸에 맞춰 잘라서 늘리기, 캔버스 기본(저품질) 축소.
- 업로드는 긴 변 2048px·JPEG 92%로 올리고, 상세에서는 원래 비율 그대로 보여 준다.
- 표시 품질은 WebP 80 한 종류, 크기 단계는 15개에서 8개로 줄였다. 상세 이미지(1440px)는 약 98KB다.
문제
프롬피드 상세 페이지의 핵심은 결과 예시 이미지다. 이걸 보고 "해 볼 만한지" 판단한다. 그런데 이 이미지가 흐릿하고 깨져 보였다. AI 결과물의 매력이 사진 품질에서 반쯤 사라졌다.
원인 세 가지
① 두 번 압축했다
관리자가 이미지를 올릴 때 브라우저에서 한 번 줄이면서 JPEG 86%로 압축했다. 화면에 보여 줄 때 Next.js 이미지 최적화가 다시 품질 75%로 압축했다. 손실 압축을 두 번 거치면 뭉개짐이 겹친다.
② 칸에 맞춰 잘라서 늘렸다
결과 이미지를 4:3 칸에 맞춰 잘라서 늘려 보여 줬다. 원래 비율과 다른 이미지는 잘리고, 잘린 만큼 늘어나서 확대된 것처럼 보였다.
③ 캔버스 기본 축소가 저품질이었다
업로드 전에 캔버스로 크기를 줄일 때, 캔버스의 기본 보간 품질은 low다. 크게 줄이면 계단 현상과 뭉개짐이 생긴다.
선택지
- 원본을 그대로 올리고 그대로 보여 준다. 화질은 가장 좋지만 용량이 크다. 휴대폰에서 느려진다.
- 화면마다 최적의 품질을 따로 준다. Vercel 무료 플랜은 이미지 변환 횟수 한도(월 약 5,000회)가 있다. 품질과 크기 조합이 늘어날수록 변환 횟수가 늘어난다.
- 업로드는 고화질로 한 번, 표시는 한 종류로. 이걸 골랐다.
내 선택
업로드: 고화질로 한 번만
ts
const MAX_SIDE = 2048;
/** 긴 변 2048px 이하 JPEG 로 줄인다 (고화질 92%). */
export async function resizeImage(file: File, quality = 0.92): Promise<Blob> {
// 이미 작은 JPEG 는 다시 압축하지 않는다
if (file.type === "image/jpeg" && long <= MAX_SIDE && file.size <= KEEP_ORIGINAL_BYTES) return file;
// ...
ctx.imageSmoothingQuality = "high"; // 기본(low)은 줄일 때 계단·뭉개짐이 생긴다
// ...
}- 긴 변 2048px, JPEG 92%로 올린다.
- 이미 충분히 작은 JPEG는 다시 압축하지 않는다. 압축을 한 번이라도 줄이려고 그렇게 했다.
- 캔버스 축소는
imageSmoothingQuality = "high"로 한다.
표시: 자르지 않는다
상세 페이지에서는 칸에 맞춰 자르지 않고 원래 비율 그대로 보여 준다.
비용: 변환 횟수를 줄인다
Next.js 이미지 최적화 설정을 무료 한도에 맞췄다.
- 품질은 WebP 80 한 종류. 화면마다 품질이 다르면 같은 이미지라도 변환이 따로 일어난다.
- 크기 단계를 15개에서 8개로 줄였다.
- 캐시는 31일. 한 번 변환한 이미지는 오래 쓴다.
- 크게 보기에서도 원본 대신 캐시된 사본을 쓴다.
ts
// next.config.ts
images: {
formats: ["image/webp"],
deviceSizes: [640, 828, 1080, 1440, 2048],
imageSizes: [128, 256, 384],
minimumCacheTTL: 2_678_400, // 31일
},결과
| 항목 | 값 |
|---|---|
| 원본 JPEG | 220~330KB |
| 상세 이미지 (1440px, WebP 80) | 약 98KB |
| 크기 단계 | 15개 → 8개 |
화질은 원본에 가깝게 유지하면서, 용량은 원본의 절반 아래로 줄었다.
배운 점
- 화질 문제는 한 군데가 아니라 흐름 전체에서 생긴다. 올리기, 줄이기, 보여 주기를 한 번에 봐야 원인이 보인다.
- 손실 압축은 한 번만. 파이프라인에 압축이 두 번 있으면 둘 중 하나는 빼는 게 맞다.
- 무료 플랜에서는 조합의 수가 곧 비용이다. 품질과 크기 종류를 줄이는 게 가장 확실한 절약이었다.
프롬피드 개발기
- 개요. SNS에서 유행하는 AI 프롬프트, 빈칸만 채워 바로 쓰게 만들었다
- 1편. 방문자가 늘어도 DB 조회는 그대로다
- 2편. AI 없이 유행 프롬프트 자동 수집기 만들었다
- 3편. 유튜브 API 무료 한도, 10,000번이 아니었다
- 4편. 복사해도 숫자가 안 올라가던 이유
- 5편. 이미지가 깨져 보였던 이유 3가지 (지금 읽는 글)
- 6편. Lighthouse 모바일 63점을 98점으로 올렸다
- 7편. 개인정보 없이 매주 알림 보내기 (웹 푸시)
- 8편. 테스트가 깨지면 배포가 안 되게 막았다
- 9편. 프롬피드 회고: 잘한 것과 아쉬운 것
프롬피드: promfeed.solfany.com