커넥트립
대표 글

이미지 초기 렌더링 99.05% 개선 (8.21s → 78ms)

커넥트립 인트로 페이지의 첫 이미지가 prod에서 최대 8.21초까지 늦게 떴다. "브라우저 캐시겠지"로 시작한 추적이 서버 SSH까지 들어갔고, 최종적으로 78ms까지 내려왔다.

배경: 인트로 페이지 도입

로그인 필수 서비스라 첫 화면에 카카오 로그인 버튼만 있고 서비스 소개가 없었다. 유입을 위해 Next/Image로 여러 PNG 이미지를 버튼으로 넘기는 인트로 페이지를 추가했다.

문제: 첫 이미지가 4~5초, 최대 8.21초

prod 배포 후 첫 이미지를 불러올 때 평균 4~5초(최대 10초 이상)가 걸렸다. 한 번 캐싱되면 새로고침 시 밀리초로 빨라졌다. (영상 기준 한 번 로딩에 8.21초.)

이상했던 건, 다른 페이지의 Next/Image(PNG→WebP 변환)는 빠른데 여기만 느렸다는 점, 그리고 Lighthouse 점수는 오히려 높게 나왔다는 점이었다.

네트워크 패널
전체 로드 7.28초가 나왔다.

시도 — 그리고 실패

  • Next/Image priority·eager — 로딩 우선순위 설정. 유의미한 개선 없음.
  • PNG → SVG 대체 — 빨라졌지만 파일이 1.2MB까지 커져 용량 문제로 부적절.
  • 일반 <img> 태그 — 초기 로딩은 다소 개선됐지만 여전히 2초+, 재로딩 성능·용량 모두 나빠짐.

세 시도 모두 근본 원인이 아니었다.

원인 발견: 브라우저가 아니라 서버 캐싱

브라우저 캐시를 지워도 재현되지 않았다. 그래서 dev 서버에 직접 SSH로 접속해 서버 캐시를 지워봤다.

ssh -i ~/key/deploy.pem ubuntu@<dev-server>
docker ps                                  # 블루/그린 컨테이너 확인
docker exec -it front-blue /bin/sh
cd /app/.next/cache/images && rm -r ./*    # 이미지 캐시 삭제

.next/cache/images를 지우고 다시 접속하니 그 n초 로딩이 그대로 재현됐다. 즉 범인은 Next/Image의 서버 측 최적화·캐싱 시간이었다. 캐싱 전후는 응답 헤더로도 확인됐다.

  • 캐싱 전: X-Nextjs-Cache: MISS
  • 캐싱 후: X-Nextjs-Cache: HIT
캐시 생성 전후
이미지 요청 후 .next/cache/images에 최적화 캐시가 생성된다.
캐싱 전 응답 헤더
캐싱 전: X-Nextjs-Cache: MISS
캐싱 후 응답 헤더
캐싱 후: X-Nextjs-Cache: HIT

1차 해결 시도: 배포 시 캐시 프리워밍

"유저가 처음 요청할 때만 느리다면, 배포 스크립트에서 최적화 이미지 링크를 미리 1회 요청해 캐싱해두면 되지 않을까?" 캐싱된 images 폴더를 지운 뒤 curl로 이미지를 미리 요청하면 캐시가 채워졌다.

SSH 접속 후 curl 프리워밍
dev 서버에서 캐시를 지운 뒤 curl로 이미지를 미리 요청(프리워밍).

그런데 문제가 있었다. Next/Image는 유저 화면 크기마다 다른 URL을 생성한다.

/_next/image?url=%2Fintro%2F1.png&w=1200&q=75
/_next/image?url=%2Fintro%2F2.png&w=1200&q=75
...

w= 값이 뷰포트마다 달라, 미리 데운 캐시가 모든 사용자에게 맞지 않았다.

최종 해결: 고정 크기 WebP + CDN

매 요청 최적화 비용·무거운 PNG·오리진 직접 전송을 한 번에 없앴다.

  • Sharp로 이미지 최적화
  • PNG → WebP 변환으로 용량 축소
  • 첫 화면 이미지를 고정 크기 WebP로 미리 만들어 AWS S3 + CloudFront CDN에서 캐싱·전송

화면 크기마다 캐시가 갈리던 문제를 고정 크기로 없애고 CDN 엣지에서 전송하니, 초기 로딩이 99.05% 개선(8.21s → 78ms).

개선 후 네트워크
개선 후 — 이미지 요청이 빠르게 완료된다.

배운 점

  • "브라우저 캐시겠지"로 넘기지 않고 서버에 직접 들어가 재현한 게 원인 규명의 핵심이었다.
  • Next/Image의 X-Nextjs-Cache MISS/HIT와 화면 크기별 URL 생성을 이해하니, 프리워밍이 왜 안 통하는지까지 설명할 수 있었다.
  • 포맷(WebP)·CDN·고정 크기는 따로면 효과가 작고, 세 가지를 함께 맞춰야 큰 개선이 난다.