infinite-scroll

이미지 최적화: 이미지 용량 97% 감소

srcset + CDN 리사이징으로 773kB → 20kB

목차

  1. 렌더링 전략 최종 선택: Streaming
  2. 이미지 최적화: 이미지 용량 97% 감소 ← 현재 문서
  3. 무한 스크롤 구현 및 리렌더링 최적화
  4. 에러 방어: 무한 재요청 방지, 빈 목록 처리

이 단계의 목표

게시글 카드에 표시되는 이미지가 원본 크기 그대로 전송되고 있었습니다. 화면에 보이는 크기보다 훨씬 큰 이미지를 다운로드하면 불필요한 트래픽이 발생하고 페이지 로딩이 느려집니다.

이미지를 화면에 실제로 표시되는 크기에 맞춰 전송하여, 불필요한 다운로드를 제거하는 것이 이 단계의 목표입니다.

이 단계의 구현 코드는 GitHub에서 확인할 수 있습니다.

1. 문제 정의

게시글 카드 너비가 300~450px 수준인데, 원본 크기 이미지를 그대로 요청하고 있었습니다.

Cloudinary URL에 변환 파라미터(w_{size},c_fill,f_auto)를 붙이면 리사이즈된 WebP 이미지를 받을 수 있으므로, 프론트엔드에서 적절한 크기를 결정하여 요청하도록 구현합니다.

2. 선택지 비교

프론트엔드가 "몇 px 이미지를 요청할 것인가"를 결정하는 방법은 3가지가 있었습니다.

JS 런타임 계산next/imagesrcset + sizes
크기 결정 주체JavaScriptNext.js브라우저
SSR 호환window 없어 서버에서 불가가능가능

JS 런타임 계산의 근본적 문제

이미지 크기를 JS로 결정하면, JS가 실행되어야 필요한 사이즈를 알 수 있고, 그래야 최적 URL을 조합할 수 있어 이미지 요청 시점이 늦어집니다.

next/image의 근본적 문제

  • 이미지 리사이징을 프론트 서버에서 수행하여 서버 부하가 증가합니다.
  • 빌드 시 리사이징된 이미지 캐시가 초기화되어 별도 대응이 필요했으나, 다른 선택지를 채택하게 되어 대응하지 않았습니다.

위 문제들이 해결되더라도 srcset + sizes 방식의 장점이 커서 진행하지 않았습니다.

3. 결정: srcset + sizes

장점

  • 이미지 요청 시점: srcset은 HTML에 포함되므로, 브라우저가 HTML을 파싱하는 즉시 적절한 크기의 이미지를 요청할 수 있습니다. JS 실행이나 별도 서버 처리를 기다릴 필요가 없습니다.
  • 서버 부하 없음: 리사이징을 프론트 서버가 아닌 CDN 측에서 처리하므로 프론트 서버에 부하가 없습니다.
  • 캐시 관리 불필요: CDN이 이미지를 캐시하므로 빌드나 배포와 무관하게 캐시가 유지됩니다.

단점

  • sizes를 문자열로 직접 작성해야 함: sizes="(max-width: 1023px) 50vw, 25vw"처럼 미디어쿼리 기반 문자열을 레이아웃에 맞게 수동으로 작성해야 합니다.
  • 다만 next/image도 유사한 불편함이 있습니다:
    • width/height를 props로 지정해야 하지만, 실제 화면에 렌더링되는 크기는 CSS가 결정하므로 이미지 크기를 props와 CSS 두 곳에서 따로 관리해야 합니다.

4. 구현

컴포넌트 구조

Image.tsx
├── BaseImage     — 모든 이미지 공통 (lazy loading, max-width: 100%)
└── OptimizedImage — Cloudinary URL → 리사이즈 srcset 변환

BoardCard sizes 설정

25vw
  • 4열 그리드 → 이미지가 뷰포트의 약 25%

CLS 방지

기존 .imageWrapperaspect-ratio: 1 / 1.2가 적용되어 있어 이미지 로드 전에도 영역이 확보됩니다.

5. 결과 — 이미지 용량 97% 감소

동일 이미지, 동일 렌더링 크기(320 x 384px) 기준:

BeforeAfter
최적화 전 이미지 품질최적화 후 이미지 품질
BeforeAfter
원본 크기1920 x 1280px400 x 267px
파일 크기773 kB20.2 kB
포맷JPEG (Cloudinary 원본)WebP (변환 파라미터)

동일 렌더링 크기에서 약 97% 용량 감소.

6. 향후 개선 가능 사항

이번 예제에서는 이미지 최적화(리사이징 + 포맷 변환)에 집중했으며, 아래 항목은 범위에서 제외했습니다.

  • 이미지 404 폴백: 게시글 이미지가 삭제되거나 URL이 유효하지 않을 경우 대체 이미지를 표시하는 기능. BaseImage에 onError 핸들러를 추가하면 모든 이미지에 일괄 적용됩니다.