pile·
프론트엔드·카카오 엔터테인먼트 FE카카오 엔터테인먼트 FE·

Next/Image를 활용한 이미지 최적화

카카오페이지 스테이지 홈에서 이미지 총용량 25MB 를 내려받던 문제를 Next/Image 로 589KB 까지 줄인 기록이다. 적용 후 오히려 20배 큰 이미지를 불러오는 사고를 겪고 원인을 규명하는 과정이 글의 절반을 차지한다.

핵심 포인트
  • 문제 상황: 홈에 이미지 약 50개, 개당 평균 500KB 로 총 25MB. 실제 렌더링 크기는 100×150px 인데 500×750px 이미지를 받고 있었고 srcset 도 쓰지 않았다.
  • Next/Image 의 세 기능은 lazy loading, 이미지 사이즈 최적화, placeholder 다.
  • 사이즈 최적화로 500KB 이미지가 40KB 로 1/12 이상 줄었고, jpeg 에서 webp 로 바뀌었다.
  • 최종 결과는 loaded 시간 1.11초 → 386ms, 총용량 25MB → 589KB(약 1/40)다.
  • 함정: `layout="fill"` 인 영역에서 3840px 이미지를 불러왔다. width·height 의 의미가 layout 값에 따라 달라지기 때문이다.
상세 정리
  • lazy loading 을 직접 구현하려면 Intersection Observer 나 scroll 이벤트로 화면 진입을 잡아야 한다(크롬 76+ 는 `loading="lazy"` 로 간단히 가능). Next/Image 는 이를 자동 적용한다.
  • 끄는 방법도 있다. 중요한 이미지는 `priority` 를 true 로 두거나 `loading="eager"` 를 주며, `priority` 쪽이 권장된다.
  • 적용 결과 50개를 받던 것이 화면에 보이는 26개만 받도록 바뀌었다.
  • 최적화 시점: 사이즈 변환과 webp 변환은 이미지 최초 요청 시 Next.js 서버에서 수행되고, 이후에는 캐시가 만료될 때까지 캐시본을 준다.
  • 캐시 만료 후에는 오래된 이미지를 먼저 주고 백그라운드에서 다시 최적화한다. 캐싱 기간은 next.config.js 설정과 CDN 응답의 Cache-Control 중 더 큰 값으로 정해진다.
  • placeholder 의 목적은 CLS(Cumulative Layout Shift) 방지다. 이미지 로드 전에도 높이만큼 영역을 잡아 레이아웃이 흔들리지 않게 한다.
  • 로컬 이미지는 빌드 타임에 width·height 가 자동 지정되고 base64 blur 이미지도 생성돼 `placeholder="blur"` 를 바로 쓸 수 있다.
  • 리모트 이미지는 Next.js 서버가 원격 서버에 직접 요청하므로, 모든 URL 을 허용하면 공격 위험이 있어 next.config.js 에 CDN host 를 명시해야 한다. 또 width·height 를 직접 넣어야 하고 blur 이미지도 `blurDataURL` 에 따로 넣어야 한다.
  • layout 속성 네 가지를 정리한다. `intrinsic`(기본, 컨테이너가 작아지면 축소), `fixed`(width·height 로 고정), `responsive`(작아지면 줄고 커지면 늘되 비율 유지), `fill`(relative 조상의 크기에 맞춤, 주로 objectFit 과 함께).
  • 사고의 원인 ①: width·height 의 의미가 `intrinsic`·`fixed` 에서는 렌더링 사이즈지만, `responsive`·`fill` 에서는 원본 이미지 사이즈이자 비율 추정용이다. 그래서 명시값보다 큰 이미지를 받을 수 있다.
  • 사고의 원인 ②로 sizes 속성 문제도 함께 짚는다.
  • 초기 요청 지연: 최초 사용자는 서버가 이미지를 만들 때까지 기다려야 하는데, 스테이지는 여러 컨테이너로 서버를 돌려 컨테이너마다 이 시간이 발생했다.
  • 해결 ①: 사이즈가 고정된 곳은 `layout="fixed"` 를 쓴다. `fixed`·`intrinsic` 은 1x·2x 두 개의 srcSet 만 만들어 최초 요청도 빠르다.
  • 해결 ②: `fill`·`responsive` 의 srcSet 은 next.config.js 의 `imageSizes`·`deviceSizes` 로 정해진다. 기본값은 지나치게 세분화돼 있어, 컨테이너 최대 너비가 정해진 스테이지는 두 값을 줄여 생성 파일 수를 낮췄다.
왜 읽나Next/Image 를 붙였는데 기대만큼 안 줄거나 오히려 커진 경험이 있는 개발자에게, layout 값에 따라 width·height 의 의미가 바뀐다는 결정적 함정을 알려준다.
카카오 엔터테인먼트 FE
카카오 엔터테인먼트 FE 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 프론트엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    항공 프론트엔드 구축기 (7/10): 창구를 하나만 두었습니다

    여기어때 항공 서비스 프론트엔드가 웹과 앱 웹뷰 두 환경에서 동일한 함수 호출로 동작하는 앱 브릿지 추상화 레이어를 설계한 과정을 다룬다. iOS·안드로이드 규약 차이와 "웹에 존재하지 않는 브릿지를 어떻게 호출하나"라는 문제를 단일 추상화 층으로 해결한 구현 사례다.

    요약 이어보기
    #react#typescript#webview+2