Next.js 이미지 최적화: next/image와 빌드 때 굽기
제품 카탈로그 데모를 만들면서 이미지 처리 방식을 정해야 했습니다. 화면에 나가는 사진은 제품 사진 몇 장과 로고뿐이고, 모두 저장소에 들어 있는 파일입니다. 요청마다 달라질 것이 없습니다.
next/image 를 쓰지 않기로 하고, 빌드 스크립트에서 한 번 굽는 쪽을 골랐습니다. 제품 사진 넉 장이 164KB 에서 31.6KB 가 됐습니다. 이 결정이 맞으려면 next/image 가 정확히 무엇을 대신해 주는지부터 알아야 했습니다.
next/image 는 이미지를 줄여 주는 컴포넌트라는 생각
next/image 를 「이미지를 자동으로 최적화해 주는 <img> 대체품」으로 이해하면, 이미지가 있는 곳마다 쓰는 것이 당연해 보입니다.
이미지 문서가 꼽는 이점을 보면 성격이 갈립니다. 하나는 시각적 안정성입니다. 이미지가 로딩되는 동안 레이아웃이 밀리는 것을 자동으로 막습니다. 다른 하나는 에셋 유연성입니다. 원격 서버에 있는 이미지까지 요청 시점에 크기를 바꿔 줍니다.
두 번째가 핵심입니다. next/image 의 최적화는 빌드 때 파일을 줄여 두는 것이 아니라, 요청이 오면 그때 변환해 주는 기능입니다. 문서는 기본 로더가 쓰는 이 경로를 Image Optimization API 라고 부릅니다.
요청 시점 변환이 필요한 경우와 아닌 경우
요청 시점 변환은 이런 곳에서 값을 합니다.
- 사용자가 올린 이미지처럼 빌드 때 존재하지 않는 파일
- 외부 저장소나 CMS 에 있어서 원본 크기를 알 수 없는 이미지
- 같은 원본을 화면 크기별로 여러 벌 내보내야 하는 반응형 이미지
반대로 저장소에 든 고정된 사진 몇 장은 이 중 어디에도 해당하지 않습니다. 빌드 때 한 번 줄여 두면 그 뒤로는 변환할 일이 없습니다. 굳이 요청 시점 변환 경로를 태우면 정적인 파일을 함수 호출로 서빙하는 구조가 됩니다. 배포 플랫폼에 따라 이 변환은 과금 대상이기도 합니다.
그래서 이 데모는 빌드 스크립트에서 sharp 로 사진을 폭 768 WebP 로 굽고(build-images.mjs), 화면에서는 평범한 <img> 로 냅니다. 원본은 저장소에 두고, 구운 결과는 배포 산출물에만 둡니다.
그 대신 직접 챙겨야 하는 것
next/image 를 쓰지 않으면 문서가 말한 첫 번째 이점, 레이아웃이 밀리지 않게 하는 일은 우리 몫이 됩니다. 문서는 정적으로 import 한 이미지에서 Next.js 가 원본의 width 와 height 를 자동으로 알아내 비율을 잡고, 그 덕분에 로딩 중 레이아웃 이동(CLS)이 생기지 않는다고 설명합니다. 원격 이미지라면 빌드 때 파일에 접근할 수 없으므로 width 와 height 를 직접 넘기거나 fill 을 쓰라고 안내합니다.
직접 굽는 쪽을 골랐다면 <img> 에 width 와 height 를 넣거나 CSS 로 비율을 고정해 같은 효과를 냅니다. 이 한 가지를 빠뜨리면 「최적화했는데 화면이 덜컹거린다」는 결과가 됩니다.
부분적으로 끄는 방법도 있다
전부 아니면 전무로 고를 필요는 없습니다. 문서에는 next/image 를 쓰되 최적화만 끄는 unoptimized 속성이 있습니다. 문서는 보안상 기본 로더가 원본을 가져올 때 헤더를 전달하지 않으므로, 인증이 필요한 이미지라면 unoptimized 를 고려하라고 안내합니다.
다른 CDN 의 변환 기능을 쓰고 싶다면 loader 를 직접 넘기거나 loaderFile 설정으로 모든 인스턴스에 적용할 수 있습니다. 「next/image 를 쓴다」와 「Next.js 의 변환 API 를 쓴다」가 같은 말이 아니라는 뜻입니다.
고를 때 묻는 두 가지
| 질문 | 그렇다면 |
|---|---|
| 이 이미지가 빌드 때 저장소에 있는가 | 있으면 빌드에서 굽고 <img> 로 충분하다 |
| 크기나 개수가 요청마다 달라지는가 | 달라지면 next/image 의 요청 시점 변환이 값을 한다 |
두 질문 모두 「아니오」인 프로젝트가 생각보다 많다고 봅니다. 회사 소개 사이트, 제품 카탈로그, 블로그처럼 이미지가 저장소에 있고 수가 정해진 경우입니다. 반대로 사용자 업로드가 있는 서비스라면 직접 굽는 파이프라인을 만드는 것보다 next/image 에 맡기는 편이 확실합니다.
한 가지 덧붙이면, 이 판단은 폰트와 반대 방향입니다. 폰트 문서에 따르면 next/font 는 폰트를 자체 호스팅해 외부 요청을 없애는 쪽으로 동작합니다. 빌드 결과에 폰트가 들어가고 브라우저는 우리 도메인에서만 받아 옵니다. 이미지와 달리 폰트는 굳이 직접 만들 이유가 적습니다.
여기까지가 확실한 부분
next/image 의 이점으로 레이아웃 이동 방지와 요청 시점 크기 변환이 꼽힌다는 점, 정적 import 시 원본 크기를 자동으로 읽는다는 점, 원격 이미지에 width·height 나 fill 이 필요하다는 점, unoptimized 와 커스텀 loader, next/font 의 자체 호스팅은 Next.js 16 문서 기준입니다. 164KB 에서 31.6KB 로 줄어든 수치는 이 데모의 빌드 결과이고, 원본 사진과 설정에 따라 달라집니다. 배포 플랫폼별 이미지 변환 과금은 플랫폼 문서에서 확인해야 합니다.
함께 읽기
- Next.js 병렬 라우트와 인터셉트 라우트: 모달 패턴과 default.js사진 목록에서 사진을 누르면 모달로 크게 보여 주고, 같은 주소를 새 탭에서 열면 전체 페이지로 보여 주는 화면이 있습니다. 주소는 /photo/123 하나인데 보이는 모양이 둘입니다.
- Next.js 데이터 가져오기 순서: 순차와 병렬, 그리고 Suspense 위치서버 컴포넌트에서 데이터를 읽는 코드는 이렇게 생깁니다.
- Next.js 다국어 경로 설계: 쿠키와 하위 경로공개하지 않고 내부에서만 여는 제품 카탈로그 데모에 언어 전환을 붙였습니다. 한국어, 영어, 중국어, 일본어, 독일어, 프랑스어 여섯 개입니다. 고른 언어는 쿠키에 저장하고, 쿠키가 없으면 브라우저의 Accept-Language 헤더를 봅니다. 주소는 나누지 않았습니다. /products/<기종> 하나로 여섯 언어를 모…
- Next.js 라우트 핸들러와 서버 함수 구분앱 라우터에서 서버로 무언가를 보내는 방법이 둘입니다. app/api/.../route.ts 에 라우트 핸들러를 만들거나, "use server" 를 붙인 서버 함수를 부르는 것입니다. 폼 하나를 붙일 때마다 어느 쪽을 쓸지 정해야 하는데, 「둘 다 서버에서 도니 아무거나」로 두면 나중에 갈립니다.
- Next.js 환경 변수: NEXT_PUBLIC_ 인라인과 로드 순서같은 Docker 이미지를 스테이징과 운영에 함께 올리는 구성이 있습니다. 환경 변수는 각 환경의 설정으로 주입합니다. 서버 쪽 값은 환경에 맞게 잘 바뀌는데, 브라우저에서 쓰는 분석 도구 ID 만 두 환경에서 같은 값이 나옵니다. 스테이징의 방문 기록이 운영 통계에 섞입니다.