커넥트립

렌더링 · 성능

대표 글

이미지 초기 렌더링 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= 값이 뷰포트마다 달라, 미리 데운 캐시가 모든 사용자에게 맞지 않았다.

2차 개선: 빠져 있던 Sharp 설치 (93.77%)

최적화가 왜 느린지 더 파보니, 프로덕션에 sharp가 빠져 있었다. sharp가 없으면 Next/Image는 느린 폴백으로 이미지를 변환한다(Next.js 공식 경고). 캐시 MISS일 때 유독 느렸던 진짜 이유였다.

sharp를 설치하니 캐시 MISS 상태에서도 초기 로딩이 8.21s → 511ms (93.77% 개선).

Sharp 설치 후 - 캐시 MISS에서도 8.21s → 511ms (93.77%).

최종 해결: 고정 크기 WebP + CDN (99.05%)

511ms도 첫 유입 유저에겐 길었고, 화면 크기마다 캐시가 갈리는 문제도 그대로였다. 그래서 Next/Image 최적화 자체를 걷어냈다.

  • PNG → WebP 변환으로 용량 축소
  • 첫 화면 이미지를 고정 크기 WebP로 미리 만들어 AWS S3 + CloudFront CDN에서 캐싱·전송
  • <Image> 대신 divbackground-image로 채워, 화면 크기별 URL 분기 자체를 제거

모든 유저가 같은 고정 크기 이미지를 엣지에서 받으니, 초기 로딩이 99.05% 개선(8.21s → 78ms).

최종 - 고정 크기 WebP + CDN으로 8.21s → 78ms (99.05%).
개선 후 네트워크
개선 후 - 이미지 요청이 빠르게 완료된다.

배운 점

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