title: Image Component (이미지 컴포넌트)
description: "Next.js 애플리케이션에서 내장된 next/image 컴포넌트를 사용하여 이미지를 최적화하는 방법에 대해 배웁니다."
url: "https://nextjs.org/docs/app/api-reference/components/image"
version: 16.1.6
lastUpdated: 2026-02-27
prerequisites:
안녕하세요! 프론트엔드 개발자를 위한 Next.js 강의에 오신 것을 환영합니다. 오늘은 웹 성능 최적화의 꽃이라고 할 수 있는 Next.js Image 컴포넌트 공식 문서를 함께 살펴볼 거예요.
웹 프로필 사이트나 북 리포트, 혹은 새로 기획하시는 뷰어 프로젝트 같은 포트폴리오를 만드실 때 이미지 최적화는 선택이 아닌 필수랍니다. 특히 실무에 가까운 최적화 경험을 기술 면접에서 아주 중요하게 평가하기 때문에, 이 문서를 완벽하게 이해해 두시면 면접관에게 훌륭한 인상을 남길 수 있을 거예요. 자, 그럼 딱딱한 원문은 던져버리고 저와 함께 구어체로 쉽게, 그리고 깊이 있게 파헤쳐 봅시다!
Next.js의 Image 컴포넌트는 HTML의 기본 <img> 태그를 확장해서 이미지를 자동으로 최적화해주는 아주 똑똑한 녀석입니다.
import Image from 'next/image'
export default function Page() {
return (
<Image
src="/profile.png"
width={500}
height={500}
alt="Picture of the author"
/>
)
}
💡 강사의 팁: 기본
<img>태그 대신<Image>를 쓰면 Next.js가 디바이스 크기에 맞춰 이미지를 리사이징하고, WebP나 AVIF 같은 용량이 적은 최신 포맷으로 알아서 변환해 줍니다. 개발자는 그저 컴포넌트를 쓰기만 하면 되죠!
컴포넌트에서 사용할 수 있는 다양한 속성(Props)들을 하나씩 살펴볼게요.
src이미지의 소스 경로입니다. 다음 중 하나를 사용할 수 있어요:
내부 경로 문자열을 쓸 수 있습니다.
<Image src="/profile.png" />
절대 경로인 외부 URL도 가능합니다. (단, 외부 URL은 꼭 remotePatterns 설정이 되어 있어야 해요.)
<Image src="[https://example.com/profile.png](https://example.com/profile.png)" />
정적(static) 파일 임포트 방식도 있습니다.
import profile from './profile.png'
export default function Page() {
return <Image src={profile} />
}
알아두면 좋은 점 (Good to know): 보안상의 이유로, 기본 loader를 사용하는 이미지 최적화 API는
src이미지를 가져올 때 헤더(headers)를 전달하지 않습니다.
만약src이미지가 인증(authentication)을 요구하는 폐쇄적인 이미지라면, 이미지 최적화를 비활성화하는 unoptimized 속성을 사용하는 것을 고려해 보세요.
altalt 속성은 스크린 리더기나 검색 엔진을 위해 이미지를 설명하는 데 쓰여요. 이미지를 사용할 수 없는 환경이거나 로딩 중 에러가 났을 때 대신 보여주는 대체 텍스트이기도 하죠.
이 속성에는 페이지의 의미를 바꾸지 않으면서 이미지를 대체할 수 있는 텍스트가 들어가야 합니다. 이미지에 대한 부가적인 설명이 아니기 때문에, 이미지 위아래에 이미 캡션으로 적혀있는 내용을 굳이 반복할 필요는 없어요.
만약 이미지가 순전히 장식용이거나 사용자에게 의미 있는 정보가 아니라면, alt 속성은 빈 문자열(alt="")로 비워두셔야 합니다.
💡 강사의 팁: 웹 접근성(a11y)은 프론트엔드 개발자로서 꼼꼼하게 챙겨야 할 기본 소양입니다. 면접에서도 "접근성을 고려해 보셨나요?"라는 질문을 받을 수 있으니, 포트폴리오 프로젝트를 하실 때 장식용 이미지는
alt=""로 처리하는 디테일을 꼭 보여주세요! 더 자세한 내용은 이미지 접근성 가이드를 참고해 보세요.
width 와 height (너비와 높이)width와 height 속성은 픽셀 단위로 이미지의 고유한(intrinsic) 크기를 나타냅니다. 이 속성은 브라우저가 이미지 공간을 미리 확보해 두고 로딩 중에 화면이 덜컹거리는 현상(Layout Shift)을 방지하기 위해 올바른 비율(aspect ratio) 을 추론하는 데 사용됩니다. 이 값들이 이미지의 실제 화면 렌더링 크기를 결정하는 건 아니에요. 렌더링 크기는 CSS가 통제하거든요.
<Image src="/profile.png" width={500} height={500} />
여러분은 다음 예외 상황을 제외하고는 반드시 width와 height 속성을 둘 다 적어주셔야 합니다:
fill 속성을 적용했을 때만약 외부에서 가져오는 이미지라 너비와 높이를 도저히 알 수 없다면, fill 속성을 사용하는 걸 적극 추천합니다.
💡 강사의 팁: 여기서 말하는 '화면 덜컹거림'이 바로 구글의 핵심 웹 지표 중 하나인 CLS(Cumulative Layout Shift) 입니다. 이미지가 늦게 뜨면서 글자들이 아래로 밀려나는 현상, 다들 겪어보셨죠?
width와height를 명시하면 브라우저가 공간을 미리 뚫어놔서 이 CLS 수치를 확 낮출 수 있습니다. 성능 최적화의 핵심 원리 중 하나예요!
fill부모 요소의 크기에 맞춰서 이미지가 꽉 차도록 팽창시키는 불리언(boolean) 속성입니다.
<Image src="/profile.png" fill={true} />
Positioning (위치 지정):
position: "relative", "fixed", 또는 "absolute" 중 하나가 지정되어 있어야 합니다.<Image> 안의 <img> 태그에는 position: "absolute"가 씌워집니다.Object Fit (오브젝트 핏):
이미지에 별다른 CSS를 주지 않으면 부모 컨테이너에 맞춰서 억지로 쫙 늘어납니다. 자르거나 비율을 유지하고 싶다면 objectFit 속성을 활용하세요.
"contain": 이미지의 비율을 유지하면서 부모 컨테이너 안에 쏙 들어가게 축소시킵니다. (빈 공간이 생길 수 있음)"cover": 부모 컨테이너를 가득 채우되, 삐져나가는 이미지 부분은 잘라냅니다.
position과object-fit에 대해 더 공부하고 싶으시면 링크를 참고하세요.
loader이미지 URL을 만들어내는 커스텀 함수예요. 이 함수는 파라미터로 몇 가지 정보를 받아서, 최종 이미지 URL 문자열을 리턴해줍니다.
'use client'
import Image from 'next/image'
const imageLoader = ({ src, width, quality }) => {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
export default function Page() {
return (
<Image
loader={imageLoader}
src="me.png"
alt="Picture of the author"
width={500}
height={500}
/>
)
}
알아두면 좋은 점: 함수 자체를 속성으로 전달받는
onLoad나 커스텀loader같은 속성을 쓰려면 해당 컴포넌트를 Client Components (클라이언트 컴포넌트)로 선언해야 합니다. 서버 컴포넌트에서는 함수를 직렬화(serialize)해서 브라우저로 보낼 수 없기 때문이에요.
개별 이미지마다 속성을 주지 않고 애플리케이션 전체의 next/image 컴포넌트에 한 번에 로더를 적용하고 싶다면 next.config.js 파일에서 loaderFile 설정을 사용할 수도 있습니다.
sizes화면 크기(breakpoints)에 따라 이미지가 어느 정도 크기로 보일지를 브라우저에게 알려주는 속성입니다. 브라우저는 이 정보를 보고 Next.js가 만들어준 여러 이미지 파일(srcset) 중에서 가장 적절한 사이즈를 골라 다운로드합니다.
import Image from 'next/image'
export default function Page() {
return (
<div className="grid-element">
<Image
fill
src="/example.png"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
)
}
sizes 속성은 다음 상황에서 꼭 써야 합니다:
fill 속성을 쓰고 있을 때만약 sizes를 생략하면 어떻게 될까요? 브라우저는 무조건 이 이미지가 화면 전체 너비(100vw)를 다 차지한다고 착각해 버립니다. 그 결과 모바일에서 봐도 될 작은 이미지 자리에 굳이 거대한 고해상도 이미지를 불필요하게 다운로드하게 되죠.
또한, sizes 속성은 srcset 목록이 생성되는 방식에도 큰 영향을 미칩니다:
sizes가 없을 때: Next.js는 고정된 크기의 이미지에 맞도록 1배수(1x), 2배수(2x) 같은 제한적인 srcset만 생성합니다.sizes가 있을 때: 반응형 레이아웃에 맞춰 최적화할 수 있도록 여러 가로 사이즈(예: 640w, 750w 등)를 촘촘하게 포함한 풀 srcset을 만들어줍니다.
srcset과sizes에 대해 더 깊이 알고 싶다면 web.dev나 mdn을 읽어보세요. 완전 프론트엔드 단골 질문입니다!
quality최적화된 이미지의 화질을 1부터 100 사이의 숫자로 정하는 속성이에요. 값이 높을수록 원본에 가깝게 깨끗하지만 파일 용량이 커지고, 값이 낮으면 용량은 확 줄어들지만 이미지가 약간 흐릿해질 수 있습니다.
// 기본 화질 설정은 75입니다.
<Image quality={75} />
만약 next.config.js에 허용된 qualities 목록을 설정해 두셨다면, 여기에 적힌 값만 사용할 수 있습니다.
알아두면 좋은 점: 만약 원본 이미지 자체가 이미 저화질이라면, 여기서
quality값을 100으로 높여봤자 화질이 마법처럼 좋아지지 않아요. 괜히 쓸데없이 파일 용량만 무거워진답니다!
style<img> 태그에 직접 CSS 인라인 스타일을 먹일 수 있게 해줍니다.
const imageStyle = {
borderRadius: '50%',
border: '1px solid #fff',
width: '100px',
height: 'auto',
}
export default function ProfileImage() {
return <Image src="..." style={imageStyle} />
}
강사의 팁/알아두면 좋은 점:
style속성으로width를 억지로 수정하려 한다면, 꼭height: 'auto'도 함께 주셔야 이미지 찌그러짐을 방지하고 원래 비율을 지킬 수 있습니다. 반응형 이미지를 만들 때 많이 하는 실수니까 꼭 주의하세요!
preload이미지를 먼저 가져올지(preload) 결정하는 불리언 속성입니다.
// 기본적으로 preload는 false입니다.
<Image preload={false} />
true: HTML의 <head> 영역에 <link> 태그를 몰래 끼워 넣어서 브라우저가 이 이미지를 미리 가져오도록(Preload) 지시합니다.false: 미리 가져오기를 수행하지 않습니다.언제 쓰면 좋을까요?
<body>에서 이미지를 발견하기 전에, 아예 <head>를 읽을 때부터 다운로드를 시작하게 만들고 싶을 때.언제 쓰지 말아야 할까요?
loading 속성을 따로 사용했을 때.fetchPriority 속성을 적용했을 때.💡 강사의 팁: 사실 대부분의 경우에는 복잡하게
preload를 쓰기보다loading="eager"나fetchPriority="high"를 사용하는 것이 더 직관적이고 권장되는 방식입니다.
priorityNext.js 16 버전부터 priority 속성은 동작 방식을 더 명확히 하기 위해 이름이 바뀌었습니다. 이 속성은 이제 더 이상 쓰지 마시고(Deprecated) 위에서 배운 preload 속성을 사용해 주세요.
loading이미지를 어느 타이밍에 로딩하기 시작할지 조절합니다.
// 기본값은 'lazy'입니다.
<Image loading="lazy" />
lazy: 이미지가 사용자의 화면(뷰포트)에 들어오기 직전의 계산된 거리까지 다가오기 전에는 굳이 로딩하지 않고 미룹니다. 불필요한 네트워크 낭비를 막아주죠.eager: 페이지상에 이미지가 어디에 있든 상관없이 화면이 열리자마자 즉시 다운로드를 시작합니다.eager는 이 이미지가 지금 당장 없으면 안 될 때만 신중하게 사용해 주세요!
loading속성에 대해 더 자세히 알아보세요.
placeholder이미지가 로딩 중일 때 사용자에게 보여줄 '빈자리 이미지(placeholder)'를 지정해서, 로딩 과정이 훨씬 부드럽고 매끄러워 보이게(Perceived loading performance) 만들어 줍니다.
// 기본값은 'empty'입니다.
<Image placeholder="empty" />
empty: 로딩 중에 아무것도 보여주지 않습니다.blur: 원본을 심하게 압축해 흐릿하게 만든 버전을 먼저 보여줍니다. 이 기능을 쓰려면 반드시 blurDataURL 속성과 짝꿍으로 사용해야 합니다.data:image/...: 직접 만든 아주 작은 용량의 Data URL 문자열을 로딩 이미지로 씁니다.예시 보러가기:
💡 강사의 팁: 인터넷이 느린 환경에서 이미지가 '툭' 하고 튀어나오면 퀄리티가 확 떨어져 보이잖아요? 뼈대만 먼저 보여주는 스켈레톤 UI나 이
blur효과를 포트폴리오에 적용해 두면, UX를 섬세하게 고려하는 훌륭한 프론트엔드 개발자라는 걸 어필할 수 있습니다.
blurDataURL이미지가 무사히 로드되기 전까지 자리를 채울 플레이스홀더로 사용할 Data URL입니다. placeholder="blur"와 세트로 쓰이거나 Next.js가 자동으로 만들어 줍니다.
<Image placeholder="blur" blurDataURL="..." />
어차피 이미지가 크게 확대되면서 블러 처리가 팍팍 들어갈 거라, 화소 수가 10px 이하인 아주 코딱지만 한 이미지를 넣는 걸 추천드려요.
자동 생성 (Automatic)
만약 src에 로컬에 있는 jpg, png, webp, avif 파일을 정적으로 임포트해왔다면, Next.js가 blurDataURL을 알아서 뚝딱 만들어 넣어줍니다. (단, 움직이는 애니메이션 이미지는 제외!)
수동 설정 (Manually set)
외부 URL(원격 이미지)이거나 동적으로 경로가 바뀌는 이미지라면 Next.js가 미리 분석할 수 없으니, 여러분이 직접 blurDataURL을 구해서 적어주셔야 해요. 어떻게 만드냐고요? 이런 도구를 활용해보세요:
blurDataURL 문자열 자체가 너무 길어지면 오히려 로딩 속도를 까먹어서 성능에 악영향을 줍니다. 작고 단순하게 유지하세요!
onLoad로딩 중이던 플레이스홀더가 싹 치워지고, 원본 이미지가 완벽하게 로딩이 끝난 직후에 딱 한 번 실행되는 콜백 함수입니다.
<Image onLoad={(e) => console.log(e.target.naturalWidth)} />
이 함수는 인자로 event 객체를 하나 받는데, 이 안의 target 속성에 접근하면 실제 그려진 쌩 HTML <img> 태그를 직접 만져볼 수 있어요.
주의할 점: 아까
loader속성에서 설명드린 것과 마찬가지로 함수를 넘기는onLoad속성 역시 클라이언트 컴포넌트(Client Components) 환경에서만 작동합니다.
onError이미지 파일이 지워졌거나 주소가 잘못돼서 로딩에 실패했을 때 실행되는 구원투수 같은 콜백 함수입니다. 에러 처리를 할 때 유용하죠.
<Image onError={(e) => console.error(e.target.id)} />
주의할 점: 이 녀석 역시 함수를 인자로 받기 때문에 클라이언트 컴포넌트에서만 사용해야 한다는 점, 잊지 마세요!
unoptimized말 그대로 이미지를 "최적화하지 않겠다!"라고 선언하는 불리언 속성입니다. 용량이 1KB도 안 되는 너무 작은 이미지나, 깨질 일이 없는 벡터 기반의 SVG 이미지, 혹은 프레임이 생명인 GIF 애니메이션 같은 애들은 괜히 서버 리소스 써가며 최적화해봤자 이득이 없거든요. 그럴 때 쓰면 아주 좋습니다.
import Image from 'next/image'
const UnoptimizedImage = (props) => {
// 기본값은 false입니다. (다 최적화한다는 뜻이죠)
return <Image {...props} unoptimized />
}
true: Next.js 서버가 이미지의 화질, 크기, 포맷을 하나도 건드리지 않고 src에 있는 원본 그대로를 사용자에게 보내줍니다.false: Next.js가 똑똑하게 이미지를 최적화해서 내려줍니다.일일이 다 적기 귀찮으시다고요? Next.js 12.3.0 버전부터는 아예 애플리케이션 전체 이미지의 최적화를 꺼버릴 수 있도록 next.config.js 설정이 추가되었습니다.
module.exports = {
images: {
unoptimized: true,
},
}
overrideSrc원래 <Image> 컴포넌트에 src 속성을 주면, 렌더링 된 <img> 태그 안에는 최적화된 경로의 srcset과 src 속성이 자동으로 생성됩니다.
<Image src="/profile.jpg" />
<img
srcset="
/_next/image?url=%2Fprofile.jpg&w=640&q=75 1x,
/_next/image?url=%2Fprofile.jpg&w=828&q=75 2x
"
src="/_next/image?url=%2Fprofile.jpg&w=828&q=75"
/>
하지만 어떤 특수한 상황에서는 이렇게 src가 막 바뀌어버리는 게 달갑지 않을 수 있습니다. 그럴 때 원래의 src 주소를 강제로 유지하고 싶다면 overrideSrc 속성을 덮어씌워 주면 됩니다.
예를 들어 기존 일반 <img> 태그를 쓰던 옛날 웹사이트를 Next.js의 <Image>로 업그레이드할 때, SEO 봇이 이미지를 다시 긁어가느라 검색 랭킹이 떨어지는 걸 막으려고 원본 src를 그대로 유지해야 하는 경우가 실무에서는 종종 발생합니다.
<Image src="/profile.jpg" overrideSrc="/override.jpg" />
<img
srcset="
/_next/image?url=%2Fprofile.jpg&w=640&q=75 1x,
/_next/image?url=%2Fprofile.jpg&w=828&q=75 2x
"
src="/override.jpg"
/>
decoding브라우저에게 이미지가 다 해독(디코딩)될 때까지 기다렸다가 다른 화면을 그릴지 말지 힌트를 주는 속성이에요.
// 기본값은 'async' 입니다.
<Image decoding="async" />
async: 이미지를 무거운 백그라운드 작업처럼 비동기적으로 해독합니다. 즉, 이미지 해독을 기다리지 않고 다른 텍스트나 UI를 먼저 화면에 재빨리 그려줍니다.sync: 화면 그리는 걸 잠시 멈추고, 이미지가 다 해독될 때까지 뚝심 있게 기다립니다. 이미지와 텍스트가 무조건 동시에 '짠' 하고 보여야 할 때 씁니다.auto: 브라우저 마음대로 가장 좋은 방식을 알아서 선택합니다.
decoding속성에 대해 더 알아보세요.
<Image /> 컴포넌트에 넘긴 나머지 자잘한 속성들은 죄다 실제 img 태그로 고스란히 전달됩니다. 단, 다음 속성은 예외예요:
srcSet: 이건 직접 쓰지 마시고, Next.js의 Device Sizes 설정을 활용하세요.onLoadingComplete경고: Next.js 14 버전부터는 폐기(Deprecated)되었습니다. 대신
onLoad를 쓰세요!
이미지 다운로드가 완전히 끝나고 플레이스홀더가 제거되었을 때 호출되던 녀석입니다. 함수 인자로 쌩 <img> 태그 요소를 반환했었죠. onLoad와 비슷하지만 이제는 역사의 뒤안길로 사라지고 있으니 굳이 배우지 맙시다.
'use client'
<Image onLoadingComplete={(img) => console.log(img.naturalWidth)} />
next.config.js 파일을 열어서 전체 이미지 컴포넌트의 작동 방식을 입맛대로 바꿀 수 있어요.
localPatterns우리 프로젝트 내에서 특정 로컬 폴더에 있는 이미지만 최적화를 허용하고, 나머지는 싹 다 막아버릴 수 있는 깐깐한 보안 기능입니다.
module.exports = {
images: {
localPatterns: [
{
pathname: '/assets/images/**',
search: '',
},
],
},
}
위처럼 설정해 두면, 반드시 /assets/images/ 폴더에서 꺼내온, 쿼리 스트링(?어쩌구)이 없는 깔끔한 이미지들만 최적화해줍니다. 만약 누군가 다른 경로의 이미지를 조작해서 최적화 서버에 무리를 주려고 하면 가차 없이 400 Bad Request 에러를 뱉어냅니다.
강사 팁: 만약
search속성을 안 적어두면 악의적인 유저가?version=12345이런 식으로 쿼리 파라미터를 계속 바꿔가며 해킹(Cache Busting 공격)을 시도할 수도 있어요. 그러니까search: ''로 텅 비워두거나, 아니면search: '?v=2'처럼 딱 정해진 값만 들어오도록 철통 방어를 해주세요.
remotePatterns이게 정말정말 중요합니다! 외부 URL에서 이미지를 끌어올 때는 무조건 remotePatterns에 허용된 도메인을 적어주셔야 해요. Next.js 서버를 악용해 아무 쓸모없는 쓰레기 이미지를 마구 최적화하게 만드는 공격을 방어하기 위함입니다.
module.exports = {
images: {
remotePatterns: [new URL('[https://example.com/account123/](https://example.com/account123/)**')],
},
}
객체 형식으로 좀 더 꼼꼼하게 설정할 수도 있어요:
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
port: '',
pathname: '/account123/**',
search: '',
},
],
},
}
와일드카드(*) 패턴:
경로(pathname)나 도메인 이름(hostname)을 지정할 때 특수문자를 써서 규칙을 유연하게 만들 수 있어요.
* (별 1개): 단일 경로 덩어리나 서브도메인 딱 1개를 매칭합니다.** (별 2개): 경로 끝부분에 계속 이어지는 여러 덩어리들이나, 도메인 맨 앞의 여러 서브도메인을 전부 다 포용합니다. (중간에 끼워 넣는 건 안 돼요!)module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**.example.com',
port: '',
search: '',
},
],
},
}
이렇게 하면 image.example.com 같은 서브도메인은 통과시켜 줍니다.
주의:
protocol,port,pathname,search속성을 아예 생략해 버리면, 기본적으로**(전부 다 허용)로 간주해버립니다. 편하긴 하겠지만 보안에는 구멍이 숭숭 뚫릴 수 있으니 가급적 구체적으로 적어주세요.
쿼리 스트링 (Query Strings):
특정 파라미터가 달린 이미지만 허용할 수도 있습니다.
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'assets.example.com',
search: '?v=1727111025337',
},
],
},
}
이렇게 하면 뒤에 무조건 ?v=1727111025337이 붙은 주소만 허용합니다.
loaderFileNext.js 내장 최적화 기능 말고, Cloudinary나 Imgix 같은 다른 이미지 전문 서버(CDN)를 쓰고 싶을 때 사용합니다.
module.exports = {
images: {
loader: 'custom',
loaderFile: './my/image/loader.js',
},
}
경로는 프로젝트 폴더 맨 위를 기준으로 적어야 하고요. 해당 파일은 반드시 URL 문자열을 뱉어내는 함수를 export 해야 합니다.
'use client'
export default function myImageLoader({ src, width, quality }) {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
굳이 설정 파일에 안 쓰고 개별 이미지 컴포넌트에
loader속성으로 직접 줘도 됩니다.
path기본적으로 Next.js가 만들어주는 이미지 URL 주소는 /_next/image 로 시작합니다. 이 앞부분 이름을 내 마음대로 바꾸고 싶다면 path 속성을 설정하세요.
module.exports = {
images: {
path: '/my-prefix/_next/image',
},
}
deviceSizes사용자 기기(스마트폰, 태블릿, 모니터 등)의 너비 기준점 목록을 설정합니다. 아까 배운 sizes 속성과 찰떡궁합으로 작동해서, 사용자 화면에 딱 맞는 크기의 이미지를 쏴주게 해주는 거죠.
아무것도 안 건드리면 기본적으로 이렇게 잡혀 있습니다:
module.exports = {
images: {
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
},
}
imageSizes이건 아까 디바이스 크기보다 훨씬 작은 '아이콘'이나 '썸네일' 사이즈를 위한 목록입니다. 이 값들과 위에서 본 deviceSizes 배열이 합쳐져서 최종적인 거대한 srcset 배열을 형성합니다.
기본값은 이렇습니다:
module.exports = {
images: {
imageSizes: [32, 48, 64, 96, 128, 256, 384],
},
}
이 imageSizes는 반드시 sizes 속성을 사용해서 "이 이미지는 화면 전체를 채우는 게 아니라 요만큼만 차지할 거야!"라고 알려준 이미지에만 쓰입니다. 그래서 배열 안의 숫자들도 전부 deviceSizes의 제일 작은 사이즈(640)보다 작게 세팅되어 있는 거랍니다.
qualities허용할 이미지 화질(quality) 값들의 리스트를 적어줍니다.
기본값은 다음과 같이 단 한 개입니다:
module.exports = {
images: {
qualities: [75],
},
}
강사 코멘트: Next.js 16 버전부터는 이 필드가 필수로 바뀌었어요. 안 그러면 해커들이 화질을 1부터 100까지 1단위로 계속 요청해서 서버를 터뜨릴 수 있거든요.
원한다면 화질 옵션을 여러 개 열어둘 수 있습니다.
module.exports = {
images: {
qualities: [25, 50, 75, 100],
},
}
이 상태에서 누군가 quality={60} 이라고 애매하게 부르면, 가장 가까운 허용 값(아마 50이겠죠?)으로 알아서 찰떡같이 바꿔서 처리해 줍니다. 아예 이상한 화질 값을 넣어서 서버로 직접 요청하면 400 Bad Request 로 튕겨내고요.
formats변환해 줄 이미지 포맷 종류를 지정합니다.
module.exports = {
images: {
// 기본값
formats: ['image/webp'],
},
}
우리가 웹사이트에 접속하면 브라우저가 "나 이 포맷들 지원해!"라고 Accept 헤더를 보내줍니다. Next.js는 그걸 읽고 최적의 포맷을 골라줍니다. 설정에 적힌 순서가 우선순위가 됩니다!
AVIF라는 최신 초강력 압축 포맷을 쓰고 싶다면 이렇게 세팅하세요. 만약 구형 브라우저라 AVIF를 지원하지 않으면 원본 포맷으로 무사히 돌아가니 걱정 마시고요.
module.exports = {
images: {
formats: ['image/avif'],
},
}
둘 다 섞어서 "일단 AVIF 최우선, 안 되면 WebP로 줘" 라고 할 수도 있습니다:
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
},
}
💡 실무 꿀팁 (Good to know):
- 웬만하면 아직은 호환성이 좋은 WebP를 기본으로 쓰시는 걸 추천해요.
- AVIF는 WebP보다 용량을 20%나 깎아주지만, 서버가 압축하는 데 걸리는 시간이 50%나 더 오래 걸려요. 즉, 누군가 처음 그 이미지를 열었을 땐 좀 느릴 수 있습니다. 하지만 한 번 변환해 두고 캐시(Cache)에 저장해 두면 그 뒤론 쾌속입니다!
- 포맷을 여러 개 활성화하면 Next.js가 AVIF 따로, WebP 따로 두 번 저장해야 하니 서버 하드디스크 용량이 그만큼 더 늘어난다는 점을 기억하세요.
minimumCacheTTL최적화된 이미지를 얼마나 오랫동안 서버 캐시에 보관할지 수명(Time to Live, 단위: 초)을 정합니다. 사실 내부 이미지는 정적 파일로 임포트(Static Image Import) 하는 편이 파일 내용물을 해싱해서 평생 지워지지 않게 캐시해 주니까 가장 좋아요.
외부 이미지를 캐시하는 기본 TTL 시간은 4시간입니다:
module.exports = {
images: {
minimumCacheTTL: 14400, // 4시간 (60초 * 60분 * 4)
},
}
서버가 이미지를 자꾸 새로 압축하느라 낑낑대는 비용을 아끼고 싶다면 한 달(31일)로 넉넉하게 늘려주세요:
module.exports = {
images: {
minimumCacheTTL: 2678400, // 31일
},
}
참고로 최종 수명은 여기서 설정한 minimumCacheTTL 값과 원본 이미지 서버에서 알려준 Cache-Control 수명 중 더 긴 시간을 따릅니다.
현재 캐시를 강제로 싹 지워버리는 버튼은 Next.js에 없기 때문에 TTL을 너무 길게 잡는 건 신중해야 합니다. 잘못 올린 이미지를 수정해도 한 달 동안 안 바뀔 수 있거든요!
disableStaticImages로컬 경로에서 이미지를 import icon from './icon.png' 형태로 가져오는 기본 기능을 아예 꺼버리고 싶을 때 사용합니다. 가끔 다른 웹팩 플러그인과 충돌이 날 때 쓰이는 비상구 옵션입니다.
module.exports = {
images: {
disableStaticImages: true,
},
}
maximumRedirects원본 이미지를 다른 서버에서 받아올 때, URL이 다른 곳으로 이사 갔다고(HTTP Redirect) 안내하면 Next.js가 기본적으로 최대 3번까지 따라가 줍니다.
module.exports = {
images: {
maximumRedirects: 3,
},
}
이걸 0으로 맞추면 URL 리다이렉션을 따라가지 않고 단호하게 끊어버립니다.
module.exports = {
images: {
maximumRedirects: 0,
},
}
maximumResponseBody기본적으로 Next.js는 원본 이미지 용량이 최대 50MB인 것까지만 허락해 줍니다. 엄청나게 큰 사진을 압축하다가 서버 메모리가 터지는 걸 막는 거죠.
module.exports = {
images: {
maximumResponseBody: 50_000_000,
},
}
프로젝트에 쓰이는 원본 파일들이 다 작다는 확신이 있다면, 이 제한을 5MB 정도로 확 낮춰서 서버의 과부하 위험을 사전에 차단하는 것도 현명한 방법입니다.
module.exports = {
images: {
maximumResponseBody: 5_000_000,
},
}
dangerouslyAllowLocalIP정말 특이한 경우지만, Next.js 서버를 사내망(Private Network) 같은 곳에서 돌릴 때 같은 내부 네트워크의 IP 주소로 들어온 이미지도 최적화해주고 싶을 수 있습니다. 하지만 외부 해커가 우리 내부 서버의 정보를 캐낼 위험이 있어서 보통은 절대 권장하지 않아요. 이름부터가 '위험하게 허용하기(dangerously)' 잖아요?
기본값은 당연히 false입니다.
module.exports = {
images: {
dangerouslyAllowLocalIP: false,
},
}
굳이 필요하시다면 true로 켤 순 있습니다.
module.exports = {
images: {
dangerouslyAllowLocalIP: true,
},
}
dangerouslyAllowSVGSVG 포맷의 이미지 렌더링을 허용하는 기능입니다.
module.exports = {
images: {
dangerouslyAllowSVG: true,
},
}
Next.js는 기본적으로 SVG를 건드리지 않아요. 왜냐면:
1. 어차피 SVG는 수학적인 벡터 방식이라 크기를 아무리 늘려도 용량이나 화질이 깨지지 않습니다.
2. SVG 파일 안에는 CSS나 자바스크립트 코드를 숨겨 넣을 수 있어서 보안 헤더 설정 없이 함부로 불렀다간 악성 코드가 실행될 수 있거든요.
그래서 src가 ".svg"로 끝나면 아까 배웠던 unoptimized 속성을 써서 최적화 과정을 그냥 건너뛰는 걸 강력 추천합니다.
만약 진짜로 SVG 최적화 옵션을 켜셨다면, 브라우저가 화면에 바로 그리지 못하게 무조건 다운로드 처리(contentDispositionType: 'attachment')를 해버리고, 스크립트 실행을 막는 보안 정책(contentSecurityPolicy)도 세트로 걸어주셔야 안전합니다.
module.exports = {
images: {
dangerouslyAllowSVG: true,
contentDispositionType: 'attachment',
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
},
}
contentDispositionType이미지 응답 헤더의 Content-Disposition 속성을 정하는 설정입니다.
module.exports = {
images: {
contentDispositionType: 'inline', // 브라우저 창에서 이미지를 띄움
},
}
contentSecurityPolicy이미지에 적용될 Content-Security-Policy(CSP)라는 보안 헤더를 설정합니다. 방금 위에서 말했듯 dangerouslyAllowSVG를 쓸 때 악성 스크립트를 원천 차단하는 방패막이 역할을 합니다.
module.exports = {
images: {
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
},
}
기본적으로 Next.js 최적화 API는 출처 모를 외부 이미지를 불러올 때 보호막을 치기 위해 파일을 무조건 다운로드하게 만드는 attachment 상태로 세팅되어 있습니다.
만약 파일을 다운받지 않고 브라우저에서 그냥 평범하게 이미지가 보이도록 렌더링하게 하려면 inline으로 바꿔주시면 됩니다.
domains경고: Next.js 14 버전부터 폐기되었습니다! 대신 훠어어얼씬 안전한
remotePatterns를 무조건 사용하세요.
예전에는 외부 이미지 주소를 허락할 때 domains 설정에 도메인 이름만 달랑 적어뒀어요. 하지만 서브도메인이나 특정 폴더 경로만 세밀하게 통제할 수가 없어서 뻥 뚫린 대문 같았죠. 클라우드 스토리지는 여러 회사가 같이 쓰는 경우가 많은데, 이러면 남의 회사 이미지까지 우리 서버가 렌더링 해버릴 수 있으니 아주 위험했습니다.
module.exports = {
images: {
domains: ['assets.acme.com'],
},
}
getImageProps오직 <img> 태그를 위한 속성(Props) 객체만 쏙 뽑아내는 아주 멋진 함수입니다. 이걸 쓰면 기본 Next.js 컴포넌트 틀에 얽매이지 않고 내가 원하는 컴포넌트나, 심지어 <canvas> 등에도 자유자재로 최적화된 이미지를 그려낼 수 있어요.
import { getImageProps } from 'next/image'
const { props } = getImageProps({
src: '[https://example.com/image.jpg](https://example.com/image.jpg)',
alt: 'A scenic mountain view',
width: 1200,
height: 800,
})
function ImageWithCaption() {
return (
<figure>
{/* 뽑아낸 속성들을 그대로 HTML img 태그에 들이붓습니다! */}
<img {...props} />
<figcaption>A scenic mountain view</figcaption>
</figure>
)
}
이 방식은 React의 useState 같은 무거운 상태 관리를 쓰지 않기 때문에 속도가 더 빠릅니다. 다만 아쉽게도 placeholder 로딩 기능은 쓸 수 없어요. 빈자리 이미지가 영원히 지워지지 않거든요!
Next.js의 <Image>는 최신 브라우저가 지원하는 네이티브 지연 로딩 (lazy loading)을 쓰는데요, 구버전 브라우저(예: Safari 15.4 이전)에서는 이 기능이 안 먹혀서 화면이 뜨자마자 로딩(eager)돼 버릴 수 있습니다. 또한 흐릿한 블러 효과도 Safari 12 이전 버전에선 아예 하얗게 빈 공간으로 보일 수도 있고요. width/height를 auto로 해놓으면 Safari 15 이전 버전에서 화면 덜컹거림(CLS) 버그가 일어날 수 있으니, 크로스 브라우징을 챙기실 거라면 각별히 주의해야 해요.
@supports (font: -apple-system-body) and (-webkit-appearance: none) { img[loading="lazy"] { clip-path: inset(0.6px) } }loading="eager"를 때려버리세요.formats 설정을 켜보거나, placeholder 속성으로 우회하세요.이미지 컴포넌트를 예쁘게 꾸미는 방법은 일반 <img> 태그와 비슷하지만, 아주 중요한 주의사항이 하나 있습니다!
styled-jsx 대신 가급적 className 속성을 쓰거나 인라인 style을 입혀주세요. 보통은 CSS 모듈(CSS Module)이나 전역 스타일시트를 클래스 네임으로 넘겨주는 방식을 추천합니다.
import styles from './styles.module.css'
export default function MyImage() {
return <Image className={styles.image} src="/my-image.png" alt="My Image" />
}
간단하게 인라인 스타일도 가능하죠.
export default function MyImage() {
return (
<Image style={{ borderRadius: '8px' }} src="/my-image.png" alt="My Image" />
)
}
만약 fill 속성을 써서 부모 영역을 꽉 채우게 할 거라면, 이미지를 품고 있는 부모 박스에 무조건 position: relative 혹은 display: block을 주셔야 화면에 제대로 나타납니다!
<div style={{ position: 'relative' }}>
<Image fill src="/my-image.png" alt="My Image" />
</div>
프로젝트 내부의 이미지를 임포트해오면 Next.js가 가로세로 크기를 알아서 캐치해줍니다. 여기에 CSS를 약간만 버무리면 아주 쉽게 쫙 늘어났다 줄어드는 반응형을 만들 수 있죠.

import Image from 'next/image'
import mountains from '../public/mountains.jpg'
export default function Responsive() {
return (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Image
alt="Mountains"
// 파일을 임포트하면 너비와 높이를 알아서 세팅해줍니다.
src={mountains}
sizes="100vw"
// CSS로 100% 꽉 차게 하고 높이는 비율에 맞춰 자동(auto)으로 늘어지게 합니다.
style={{
width: '100%',
height: 'auto',
}}
/>
</div>
)
}
외부 이미지 주소를 끌어올 때는 Next.js가 원본 크기를 미리 알 도리가 없으니, 우리가 직접 width와 height 값을 적어줘서 원본의 '비율(Aspect ratio)'을 알려줘야 합니다!
import Image from 'next/image'
export default function Page({ photoUrl }) {
return (
<Image
src={photoUrl}
alt="Picture of the author"
sizes="100vw"
style={{
width: '100%',
height: 'auto',
}}
width={500}
height={300}
/>
)
}
fill (비율을 모를 땐 fill 활용하기)외부에서 오는 거라 이미지의 가로세로 비율조차 전혀 모르겠다고요? 당황하지 마시고 fill 속성을 킨 다음 objectFit="cover"를 발라주세요. 빈틈없이 영역을 꽉꽉 채워줍니다!

import Image from 'next/image'
import mountains from '../public/mountains.jpg'
export default function Fill() {
return (
<div
style={{
display: 'grid',
gridGap: '8px',
gridTemplateColumns: 'repeat(auto-fit, minmax(400px, auto))',
}}
>
{/* 부모 요소에 반드시 position: relative를 주셔야 합니다! */}
<div style={{ position: 'relative', width: '400px' }}>
<Image
alt="Mountains"
src={mountains}
fill
sizes="(min-width: 808px) 50vw, 100vw"
style={{
objectFit: 'cover', // cover 말고도 contain, none 도 있어요
}}
/>
</div>
{/* 이런 식으로 그리드에 이미지를 쭉쭉 추가할 수 있어요... */}
</div>
)
}
CSS의 background-image처럼 웹페이지 바닥에 이미지를 전면으로 쫘악 깔고 싶을 때도 fill이 찰떡입니다.

import Image from 'next/image'
import mountains from '../public/mountains.jpg'
export default function Background() {
return (
<Image
alt="Mountains"
src={mountains}
placeholder="blur"
quality={100}
fill
sizes="100vw"
style={{
objectFit: 'cover',
}}
/>
)
}
더 다양하고 재밌는 예제가 보고 싶으시면 공식 데모 페이지인 Image Component Demo를 둘러보시는 것도 아주 좋은 공부가 될 거예요.
외부 URL 문자열을 넘겨주면 그게 원격 이미지입니다.
import Image from 'next/image'
export default function Page() {
return (
<Image
src="[https://s3.amazonaws.com/my-bucket/profile.png](https://s3.amazonaws.com/my-bucket/profile.png)"
alt="Picture of the author"
width={500}
height={500}
/>
)
}
서버가 빌드되는 동안에는 이 사진을 뜯어볼 수 없으니, 여러분이 손수 width와 height, 그리고 여유가 된다면 blurDataURL도 제공해 주셔야 합니다.
다시 강조하지만 이 사이즈를 적어주는 이유는 화면 덜컹거림 방지용이지 실제 화면을 렌더링 할 물리적 크기를 못 박는 게 아니에요!
안전한 최적화를 위해 꼭 next.config.js에 주소 패턴을 등록하는 것 잊지 마시고요! 악의적인 접근을 막기 위해 최대한 구체적으로 폴더 경로까지 지정하는 것이 실무급 팁입니다.
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 's3.amazonaws.com',
port: '',
pathname: '/my-bucket/**',
search: '',
},
],
},
}
화면 테마가 어두운 다크모드냐 밝은 모드냐에 따라 아예 이미지를 통째로 바꿔치기 하고 싶을 때가 있죠? 그럴 땐 <Image> 두 개를 동시에 숨겨놓고 CSS의 미디어 쿼리(media query)로 스위치를 껐다 켰다 하시면 됩니다.
.imgDark {
display: none;
}
@media (prefers-color-scheme: dark) {
.imgLight {
display: none;
}
.imgDark {
display: unset;
}
}
import styles from './theme-image.module.css'
import Image, { ImageProps } from 'next/image'
type Props = Omit<ImageProps, 'src' | 'preload' | 'loading'> & {
srcLight: string
srcDark: string
}
const ThemeImage = (props: Props) => {
const { srcLight, srcDark, ...rest } = props
return (
<>
<Image {...rest} src={srcLight} className={styles.imgLight} />
<Image {...rest} src={srcDark} className={styles.imgDark} />
</>
)
}
강사의 팁: "어? 이미지 두 장을 넣으면 네트워크 다운로드를 두 번 해서 손해 아니야?" 하실 수 있어요. 하지만
loading="lazy"속성의 마법 덕분에, 화면에 그려져서 눈에 보여야 하는 딱 하나의 이미지만 다운로드를 받게 됩니다! 이 상태에서loading="eager"나preload를 걸면 눈치 없이 두 장을 다 다운받아버리니까 대신fetchPriority="high"속성으로 중요도만 살짝 올려주세요.
화면 크기에 따라 아예 구도나 내용이 다른 사진을 내보내는 걸 전문 용어로 아트 디렉션(Art Direction)이라고 합니다. 아까 배운 getImageProps()를 활용해서 HTML의 <picture> 태그와 조합하면 환상적으로 구현해 낼 수 있죠.
import { getImageProps } from 'next/image'
export default function Home() {
const common = { alt: 'Art Direction Example', sizes: '100vw' }
const {
props: { srcSet: desktop },
} = getImageProps({
...common,
width: 1440,
height: 875,
quality: 80,
src: '/desktop.jpg',
})
const {
props: { srcSet: mobile, ...rest },
} = getImageProps({
...common,
width: 750,
height: 1334,
quality: 70,
src: '/mobile.jpg',
})
return (
<picture>
<source media="(min-width: 1000px)" srcSet={desktop} />
<source media="(min-width: 500px)" srcSet={mobile} />
<img {...rest} style={{ width: '100%', height: 'auto' }} />
</picture>
)
}
더 나아가서, 렌더링 된 srcSet 문자열을 CSS의 image-set() 함수로 가공하면, 백그라운드 이미지로 쓰는 녀석들까지도 화면 밀도(해상도)에 맞춰 완벽하게 대응할 수 있어요! 프론트엔드 장인으로 거듭나는 순간입니다.
import { getImageProps } from 'next/image'
function getBackgroundImage(srcSet = '') {
const imageSet = srcSet
.split(', ')
.map((str) => {
const [url, dpi] = str.split(' ')
return `url("${url}") ${dpi}`
})
.join(', ')
return `image-set(${imageSet})`
}
export default function Home() {
const {
props: { srcSet },
} = getImageProps({ alt: '', width: 128, height: 128, src: '/img.png' })
const backgroundImage = getBackgroundImage(srcSet)
const style = { height: '100vh', width: '100vw', backgroundImage }
return (
<main style={style}>
<h1>Hello World</h1>
</main>
)
}
| 버전 (Version) | 업데이트 내역 (Changes) |
|---|---|
v16.1.2 | maximumResponseBody 환경설정 옵션이 새롭게 추가되었습니다. |
v16.0.0 | qualities의 기본값이 [75]로 바뀌었고, preload 속성이 생겼습니다. 기존의 priority 속성은 폐기되었으며, dangerouslyAllowLocalIP와 maximumRedirects 설정이 추가되었습니다. |
v15.3.0 | remotePatterns 안에서 URL 객체들의 배열을 지원하기 시작했습니다. |
v15.0.0 | contentDispositionType 설정의 기본값이 attachment로 변경되어 보안이 한층 강화되었습니다. |
v14.2.23 | qualities 설정 기능이 생겼습니다. |
v14.2.15 | decoding 속성과 localPatterns 설정이 새롭게 추가되었습니다. |
v14.2.14 | remotePatterns.search 설정으로 쿼리 스트링까지 통제할 수 있게 되었습니다. |
v14.2.0 | overrideSrc 속성이 추가되어 원본 URL을 고집할 수 있게 되었습니다. |
v14.1.0 | getImageProps() 함수가 정식 버전(stable)으로 자리 잡았습니다. |
v14.0.0 | onLoadingComplete 속성과 domains 설정이 완전히 구시대의 유물이 되어 폐기(deprecated) 처리되었습니다. |
v13.4.14 | placeholder 속성에 data:/image... 형식의 문자열을 꽂아 넣을 수 있게 되었습니다. |
v13.2.0 | contentDispositionType 설정 기능이 추가되었습니다. |
v13.0.6 | ref 속성(DOM 요소에 접근하기 위한 속성) 지원이 추가되었습니다. |
v13.0.0 | 기존의 next/image를 가져오던 방식이 next/legacy/image로 밀려났고, 실험적이던 next/future/image가 진짜 next/image의 왕좌를 차지했습니다. (물론 손쉽게 바꿔주는 코드 변환 도구(codemod)가 지원됩니다!) 또한 기존에 이미지를 씌우고 있던 <span> 껍데기 태그가 사라져서 코드가 짱짱 깔끔해졌고, 쓸모없어진 옛날 속성들이 대거 삭제되었습니다. |
v12.3.0 | remotePatterns와 unoptimized 설정이 정식 기능(stable)으로 안정화되었습니다. |
v12.2.0 | remotePatterns와 unoptimized가 실험실(experimental) 모드로 첫 선을 보였고, layout="raw" 속성이 제거되었습니다. |
v12.1.1 | style 속성을 사용할 수 있게 되었습니다. |
v12.1.0 | dangerouslyAllowSVG와 contentSecurityPolicy 설정으로 SVG를 통제할 수 있게 되었습니다. |
v12.0.9 | 지연 로딩을 위한 lazyRoot 속성이 추가되었습니다. |
v12.0.0 | formats 설정과 꿈의 포맷인 AVIF 지원이 추가되었습니다! 기존 외부 <div> 래퍼는 <span>으로 얌전하게 바뀌었습니다. |
v11.1.0 | onLoadingComplete와 lazyBoundary 속성이 추가되었습니다. |
v11.0.0 | 로컬 경로에서 이미지를 정적으로 임포트해오는 기능과, placeholder 및 blurDataURL 속성이 등장했습니다. (블러 처리 기능 혁명!) |
v10.0.5 | 커스텀 URL을 만들 수 있는 loader 속성이 생겼습니다. |
v10.0.1 | 이미지 레이아웃을 조절하는 layout 속성이 추가되었습니다. |
v10.0.0 | 전설의 시작, next/image 컴포넌트가 세상에 처음 공개되었습니다! |
더 많은 전체 문서 목록이 궁금하시다면 사이트맵 가이드(sitemap.md)를 확인하시고, 사용할 수 있는 모든 문서의 색인은 llms.txt를 참조해 주세요.
자, 수고하셨습니다! 분량이 꽤 길었지만, 이 문서의 내용만 제대로 소화하신다면 기술 면접이나 실제 포트폴리오 프로젝트에서 이미지 최적화만큼은 자신 있게 대답하실 수 있을 거예요. 언제든 또 궁금한 점이 생기면 편하게 물어보세요!