
Updated at : Fri Oct 30 2026
Next.js는 자체적으로 Image 컴포넌트를 지원한다. 이를 사용하면 크기 최적화, layout shift 방지 등을 지원해준다고 하니, 사용하지 않을 이유가 없다. 다만 사용하는 것(이미지를 띄우는 것) 자체는 쉬우나, 최적화가 제대로 이루어지도록 사용하려면.. 좀 더 깊은 이해가 필요하다고 생각된다. 나도 아직도 제대로 이해하고 잘 사용하고 있는게 맞나.. 싶다. 따라서 이 문서는 지속적으로 업데이트될 것이며, 오류가 있을 수 있다는 비판적인 시선으로 보도록 하자.
Next.js Image 컴포넌트에는 width, height 프로퍼티를 주입할 수 있다. width, height는 너무나 친숙한 속성이고, 보통 영역의 크기를 결정하는데 사용된다. 예를 들어 화면의 500x500 만큼의 영역에 이미지를 그리고 싶었다면, <Image src="/img.png" alt="img" width={500} height={500} />와 같이 사용하면 그 목적이 달성된다. 그런데 이게 올바르게 사용한 것이 맞을까?
다음은 Next.js 공식문서에서 설명하는 width, height에 대한 설명이다.
The width and height properties represent the intrinsic image size in pixels. This property is used to infer the correct aspect ratio used by browsers to reserve space for the image and avoid layout shift during loading. It does not determine the rendered size of the image, which is controlled by CSS.
한 줄씩 해석하며 이해해 보자.
The width and height properties represent the intrinsic image size in pixels.
이 구문을 이해해보자면, width와 height 속성은 이미지의 실제 크기를 나타낸다고 해석할 수 있다. 어, 그러면 이 속성들에는 내가 화면에 그리고자 하는 영역 크기에 대한 값을 넣는 속성이 아닌 것인가? 만일 앞서 사용한 img.png가 실제로 1200x600의 크기를 갖는 이미지였다면 이 값들을 넣어야 했던 것일까?
그러나 1200,600값을 넣어 실험해보면, 화면에 1200x600 만큼의 영역을 차지하는 것을 목격하게 될 것이다. 그렇게 되면, 뭐야 width, height 속성은 분명 영역의 크기를 잡는게 맞는데? 하고 생각하게 될 것이다. 결론부터 말하면, 문서에서 말하는대로 원본 사이즈를, 1200,600이라는 값을 넣어야하는 것이 맞다.
Nextj.js는 이 값들을 원본 사이즈로 인식하여 이미지 최적화에 사용한다. 또한 브라우저 역시 이 값들을 활용하여 레이아웃 시프팅 방지를 위한 종횡비 계산에 사용한다. 그것이 두 번째 줄 This property is used to infer the correct aspect ratio used by browsers to reserve space for the image and avoid layout shift during loading.에서 말하는 내용이다. 이러한 목적을 갖는 프로퍼티들이기에, 여기에는 원본 사이즈 값들을 입력해야 한다.
어 그렇다면 왜 해당 값에 넣은 만큼 실제 영역을 차지하면서 그려진 거지? 그리고 내가 원하는 영역만큼 그리게 만들려면 그럼 어떻게 하라는거지? 설마 모든 원본 이미지 크기를 그에 맞게 준비하라는 말도 안되는 소리인가?
해당 프로퍼티에 값에 넣은 것이 그대로 영역으로 차지하는 이유는 Next.js Image 컴포넌트 역시, HTML img 태그를 활용하는 것이며 해당 속성을 그대로 img 태그에 전달하기 때문이다. 알다시피 HTML img 태그에는 width, height 프로퍼티가 허용되며 별도의 css값이 없다면 이 값들이 그대로 사용되게 된다. mdn 공식문서를 확인해보자.
따라서 원하는 영역만큼 이미지를 그리기 위해서는 별도의 css 값을 부여해서 크기를 조절해야 한다. 그것이 세번째 줄 It does not determine the rendered size of the image, which is controlled by CSS.로 부터 얻을 수 있는 해석이다.
따라서 앞서의 예시는 다음과 같이 수정해 사용하여야 한다.
<Image
src={'/img.png'}
alt="img"
width={1200} // 원본 사이즈
height={600} // 원본 사이즈
style={{ width: 500, height: 500 }} // 화면에 표현할 사이즈
/>
공식 문서에서는 이미지 삽입 방법을 3가지로 안내한다. 그 방안들은 다음과 같다.
이미지 최적화를 다루는 문서에서, 이들은 로컬(Local) 이미지와 리모트(remote) 이미지로 분류하여 설명한다. 당연하게도 Static import와 Internal path 방식은 로컬 이미지, External URL 방식은 리모트 이미지이다.
물론 틀린 말은 아닌데, 나는 이보다는 static 이미지와 url 이미지로 구분하는게 좀 더 낫지 않나 싶다. 애초에 공식 문서도 Internal path 방식을 static화 할 수 있도록 안내하고 있다.
Static import 방식을 사용하면, Next.js는 자동적으로 이미지를 분석하며 빌드시 이미지 최적화도 진행된다. 이 이미지 분석시 원본 이미지 크기도 분석하기 떄문에, 이 방식을 사용하면 위에서 확인한 width, height 프로퍼티를 기입하지 않고 <Image src={img} alt="img" />와 같이 사용가능해진다. 사용하기도 좋고 최적화에도 좋기 때문에 여러모로 권장되는 방식이다.
Static import 방식이 권장되기에, Next.js에서는 Internal path 방식도 다음과 같이 변경하여 사용하라고 말한다. 이 방식을 사용하면, Internal path 방식도 이미지가 분석되게 되고 별도의 width,height 기입없이 사용할 수 있다는 듯 하다.
// 서버 컴포넌트에서만 가능
const { default: image } = await import( `../content/blog/images/${imageFilename}`)
return <Image src={image} alt={alt} />
그런데 이 말인 즉, 위와 같이 별도의 static화 작업이 없다면, Internal path 방식은 External URL 방식과 다를게 없어진다는 말과 같다.
Since Next.js does not have access to remote files during the build process, you'll need to provide the width, height and optional blurDataURL props manually. The width and height are used to infer the correct aspect ratio of image and avoid layout shift from the image loading in.
위의 구문은 공식문서에서 말하는 리모트 이미지에 대한 설명으로, 빌드 과정에서 확인할 수 없기에 width, height 값을 기입해주어야 한다는 것을 말하고 있다. 그리고 Internal path에도 이 특성이 똑같이 적용된다. width, height 작성없이 Internal path 방식을 사용해보면 바로 오류가 나며 중지되는 Next.js를 볼 수 있다.
따라서 Internal path 방식은 어떻게 사용하느냐에 따라서 static에 가까울 수도 external에 가까울 수도 있는 녀석이기 때문에, 그냥 static 이미지와 url 이미지가 있다고 구분하는게 낫지 않나.. 싶다.
위의 내용으로 기본적인 사용법과 개념에 대해 알았으니, 간단한 사용에는 문제가 없다. 그러나 조금만 실무적으로 들어가도 곤란한 일들이 생긴다. 가장 먼저 발생할 문제는 다음과 같다.
사용되는 이미지가 두 세개 정도면 별 생각없이 찾아서 기입하고 넘어갈 수 있다. 그런데 이 개수가 매우 많다고 가정하자. 나는 벌써부터 어지럽다. 심지어 이 방식을 사용시, 종횡비를 맞추지 않으면 레이아웃 쉬프트 문제가 발생할 수도 있다.
앞서 예시를 그대로 사용해 원본 크기가 1200x600이고 이를 500x500으로 화면을 그렸다고 하자. 이 수치들로 실제 사용해보면 레이아웃 시프트가 발생할 것이다. 종횡비가 2:1인데, 넓이 500에 종횡비를 감안한 높이 250만큼을 점유하고 있다가, 차후에 높이 500으로 변경될 것이기 때문이다.
이 영역 점유 계산법은 빠른 로딩을 위해 활용되는 방식으로, 바로 확인되는 HTML 태그에 작성된 값을 이용해 종횡비 뽑아내고, 그 후 css를 확인하여 얻은 width를 이용하는 방법이다. css를 확인했는데도, css에 작성된 height를 이용하지 않는 건, 그렇게 하면 더 느려진다는 듯 하다..
따라서 이 쉬프트 현상까지 고려하여 우리가 해야하는 작업은 원본 이미지의 사이즈를 일일히 확인해 기입해야 하는 것에, 추가로 종횡비를 생각한 원본 이미지 준비 or 화면 구성이 되어야 한다는 것을 뜻한다. 이것까지도 괜찮다면 사용하면 된다. 나는 도저히 못한다.
여기서 구세주 같이 fill 속성이 등장한다. 이 속성을 사용하면 더이상 이미지에 width, height 값을 입력할 필요가 없게 되고, img 태그에는 absolute 및 width:100%, height:100% 속성이 부여된다. 따라서 우리는 부모 컨테이너를 만들어 이미지를 감싸주고, 부모 컨테이너에 relative 및 width, height 값을 지정만 해주면 되게 된다.
<div style={{ position: 'relative', width: 500, height: 500}}>
<Image src={img} alt="img" fill /> // static 이미지로 사용시
<Image src={'/img.png'} alt="img" fill /> // url 이미지로 사용시
</div>
이 방법을 사용하면 레이아웃 쉬프트 현상은 아예 발생하지 않게 된다. 왜냐하면 근본적으로 쉬프트가 발생할 원인이었던 요소를 사용하지 않는 셈이 되기 때문이다.
img나 video, iframe 등은 외부 자원(이미지, 동영상 등)에 의해 콘텐츠가 대체되는 대체 엘리먼트(Replaced Element)이다. 따라서 브라우저는 실제로 이것을 다운로드하기 전까지 어느정도의 크기인지는 알 방법이 없어 그냥 아주 작은 값으로 처리한다. 그 후 다운로드가 완료되어 그 크기가 표현되면 그 차이 때문에, 레이아웃이 틀어지게 되는 쉬프트 현상이 발생하던 것이다. 이를 해결하기 위해 애초부터 영역을 점유해놓도록 만들어 다운로드가 완료되더라도 그 차이가 없어 보이게 만드는 기법이, 방금까지 다룬 종횡비를 활용하는 방법이었던 것이다.
그런데 이제 img 태그가 아예 공간 점유를 포기하고, 대신 그 영역을 div가 차지해주게 되는 방식으로 변경된 것이다. div, p, span 등의 일반 엘리먼트들은 바로 그 영역을 확정할 수 있다. 따라서 더이상 영역을 미리 점유할 필요도 없고, 영역이 이미 확정되어 있기 때문에 쉬프트가 발생할 일도 원천 차단되는 셈이다.
따라서 더이상 이미지는 width, height 프로퍼티를 필수적으로 입력 받지 않아도 되는 것이다!
대체 엘리먼트 및 일반 엘리먼트의 영역 차지에 관해서는 공식 문서에서 더 자세히 확인할 수 있다.
https://developer.mozilla.org/ko/docs/Web/CSS/Guides/Images/Replaced_element_properties
https://developer.mozilla.org/ko/docs/Web/CSS/Guides/Display/Block_and_inline_layout#%EB%B8%94%EB%A1%9D_%EC%84%9C%EC%8B%9D_%EC%83%81%ED%99%A9%EC%97%90_%EC%B0%B8%EC%97%AC%ED%95%98%EB%8A%94_%EC%9A%94%EC%86%8C
조금 의아할 수 있다. 쉬프트 현상을 해결하니 width, height 값을 입력하지 않아도 된다고? 원래 그 목적만 있던 속성인가? 그렇지 않다. 이미지 최적화에도 사용되는 요소이다.
width, height 프로퍼티는 원본 이미지의 크기를 입력하던 곳이었음으로, static 이미지의 경우 알아서 그 값을 가져가니 문제가 되지 않을 것이다. 반면 url 이미지인 경우, width, height 값을 직접 기입해줄 때와 아닐 때의 차이가 있다. 가장 큰 차이점은 여러 해상도 버전(=srcset)을 만드는 방법에서 나타난다고 한다. 다만.. 뭐가 어떻게, 얼만큼 차이가 나는지는 아직 잘 모르겠다.
확실한 것은 이 차이를 최소화하도록 돕는 것이 이미지에 sizes 속성을 부여하는 행위라는 것이다. 앞서 보여준 예시처럼, sizes 속성 없이도 사용할 수 있으나, 이는 알아서 sizes 값으로 100vw가 기입된 것 뿐이다.
이 값을 명시하지 않을 경우, Image with src "/img.png" has "fill" but is missing "sizes" prop 와 같이 경고를 보내오는 브라우저를 만나게 될 것이다.
이 속성의 용도는 해당 이미지가 실제 화면에서 어느 정도의 크기를 차지하는지에 대한 정보를 기입하는 영역이다. 따라서 만일 실제로는 100px 만큼의 영역에서 사용되는 이미지였으나 값을 기입하지 않았다면, sizes가 100vw로 계산되어 엄청나게 큰 이미지를 요청하게 된다. 이는 대충만 생각해보아도 엄청난 낭비라는 것을 느낄 것이다.
따라서 이 fill 속성을 이용할 경우, sizes를 함께 사용해서 최적화를 돕는 것이 사실상 반 필수이다. 이 sizes 값에는 동적 대응을 위하여 여러 breakpoint들을 줄 수도 있다. 아래 예시를 참조하자.
<Image
src="/example.png"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
여기서부터는 내가 개인적으로 사용하는 방식이며, 잘못된 방식은 아닌지 or 최적화를 깨는 것은 아닌지 아직 검증해보지는 못했다. 따라서.. 참고만 하자.
최적화를 깨는 방식이었다. 아래의 방법은 아직 제대로 인식이 안되기에 sizes 값으로 모두 100vw를 할당하는 것과 다를바가 없게 된다. 따라서 하단의 글은 무시하고 sizes를 적절히 잘 사용토록하자.
그럼에도 아래 글을 삭제하지 않는 것은.. 추후 Next.js 및 브라우저가 발전하게 되면, 아래의 방법을 사용할 수 있을지도 모르니까.. ㅜㅜ
위의 sizes를 사용하는 예시를 보고 어떤 생각이 들었는가? 아하, 저렇게 사용하면 되겠군 하고 말았나?
나는 아니다. 아니, 이건 또 뭐야. width, height에 원본 이미지 크기 입력하는 것도 번거로웠는데, 모든 이미지마다 다 이렇게 분기를 나누어 작성하라고? 장난하냐? 라고 생각했다.
그러나.. 가만히 생각해 보면, 이 항목에는 실제 이미지가 사용되는 크기를 입력하는 것이라 하였고? fill 속성을 사용하면? 그 크기는 사실상 부모 컨테이너의 크기라는 것이다. 따라서 부모 컨테이너의 크기를 100%와 같이 어떠한 비율로 바로 갖고 올 수 있는 방법이 없으려나.. 확인해보았고 그 방법을 찾았으니 바로 cqw라는 비율 값을 활용하는 것이다.
mdn 공식문서: CSS container queries
위 링크는 container query를 다루는 공식문서이니 자세한 내용은 이를 참고하도록 하자. 이 기능을 이용하면 더이상 sizes 값을 매번 고민할 필요가 없어진다. 부모 컨테이너의 전체 넓이로 지정한 셈이니까. 동적 변경 역시? 부모의 크기를 변경되면 그 값은 알아서 변경되는 것이다. 아래의 예시를 참조하자.
<div style={{ position: 'relative', containerType: 'inline-size', width: 200, height: 200 }}>
<Image src={'/img.png'} alt="img" fill sizes="100cqw" />
</div>
나는 위의 방법을 사용하여 모든 이미지를 그린다. 그럼 원하는 크기를 부모 컨테이너에 넣고, 이미지에는 src, alt 값 정도만 변경해주는 것으로 작업이 끝나게 된다.