들어가기에 앞서 Next.js 이미지 컴포넌트에 대해 얼마나 알고 있는지 알아보기위해 warm-up quiz를 준비했습니다.
정답 : o, x, o, x, o, x
export default function ImagePage() {
return <img src="/cat.jpg" alt="cat" />;
}
import Image from 'next/image';
import cat from '@/public/cat.jpg';
export default function ImagePage() {
return (
<Image
src={cat}
alt="cat"
quality={75} // 이미지 품질, 기본값 75
/>
);
}
이미지 이름이 http://localhost:3000/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcat.0d7457c2.jpg&w=3840&q=75
로 바뀜
이미지 형식이 jpg에서 웹친화적인 webp로 변경
파일 사이즈가 675kB로 감소
fast 3g 환경에서 11.8초 소요
Image 컴포넌트만 사용했을 뿐인데 굉장한 성능향상을 확인 할 수 있다.
import Image from 'next/image';
import cat from '@/public/cat.jpg';
export default function ImagePage() {
return <Image src={cat} alt="cat" size="100vw" />; // 이미지가 100% view width를 차지한다는 의미
}
size="100vw"
하나 넣었다고 이미지 크기가 다시 한번 1/6로 줄어듬https://vercel.com/docs/image-optimization/limits-and-pricing
정말 중요한 내용인데 nextjs 공식 문서가 아닌 vercel 공식문서 보면 다음과 같이 요금제를 확인 할 수 있다.
그리고 실제 dashboard에서는 지금까지 프로젝트에서 사용한 총 이미지 source의 개수를 확인 할 수 있다.
여기에서 말하는 source images란 개별 이미지를 말함.
import Image from 'next/image';
import cat from '@/public/cat.jpg';
export default function ImagePage() {
return (
<>
<Image src={cat} alt="cat" size="100vw" />
<Image src={cat} alt="cat" size="100vw" />
<Image src={cat} alt="cat" size="100vw" />
<Image src={cat} alt="cat" size="100vw" />
<Image src={cat} alt="cat" size="100vw" />
</>
);
}
import Image from 'next/image';
import catSmall from '@/public/cat-sm.jpg';
import catMedium from '@/public/cat-md.jpg';
import catLarge from '@/public/cat-lg.jpg';
export default function ImagePage() {
return (
<>
<Image src={catSmall} alt="cat" />
<Image src={catMedium} alt="cat" />
<Image src={catLarge} alt="cat" />
</>
);
}
만약 웹사이트에서 사용자 이미지가 많아질 경우 pro요금제(월 2만5천원) + 1000장당 6천원이므로 비용 부담이 상당함
import Image from 'next/image';
import cat from '@/public/cat.jpg';
export default function Home() {
return <Image src={cat} alt="cat" unoptimized />; // source images 카운트가 안까인다.
}
next/image에서 정의한 정적 이미지의 type은 아래와 같다.
export interface StaticImageData {
src: string;
height: number;
width: number;
blurDataURL?: string;
blurWidth?: number;
blurHeight?: number;
}
이를 화면에서 확인해 보면 아래와 같다.
import Image from 'next/image';
import cat from '@/public/cat.jpg';
export default function Home() {
return (
<>
<Image src={cat} alt="cat" size="100vw" />
<div className="p-4">
<p>src : {cat.src}</p>
<p>height : {cat.height}</p>
<p>width : {cat.width}</p>
<p>blurDataURL : {cat.blurDataURL}</p>
<p>blurHeight : {cat.blurHeight}</p>
<p>blurWidth : {cat.blurWidth}</p>
</div>
</>
);
}
정적 이미지 설정시 자동으로 height와 width가 설정된 것을 확인 할 수 있다. 그러나 이는 웹페이지 상에서 보여질 width와 height가 아니라 원본의 width와 height다.
웹페이지에서 사용할 이미지는 대부분 원본보다는 작은 크기를 사용하기 때문에 width
속성을 500으로 조절하였다.
size
속성은 일단 무시
import Image, { StaticImageData } from 'next/image';
import cat from '@/public/cat.jpg';
export default function Home() {
return (
<>
<Image src={cat} alt="cat" sizes="100vw" width={500} /> // width 를 500px로
<div className="p-4">
<p>src : {cat.src}</p>
<p>height : {cat.height}</p>
<p>width : {cat.width}</p>
<p>blurDataURL : {cat.blurDataURL}</p>
<p>blurHeight : {cat.blurHeight}</p>
<p>blurWidth : {cat.blurWidth}</p>
</div>
</>
);
}
width
를 줄였다고 이미지의 파일 사이즈(102kB)가 줄어들진 않는다.
정적 이미지는 next가 이미 가로세로 비율을 알고 있기 때문에 width
또는 height
속성 둘중 하나만을 입력해도 된다.
width
, height
속성을 통해 로드 될 이미지의 크기를 미리 지정하여 CLS(cumulative layout shift)를 막을 수 있다.
정리해보자면 width
, height
속성은 CLS방지 + 화면에 보여질 이미지의 width와 heght를 정의하는 역할을 한다.
정적 이미지 설정시 blur와 관련된 값들(blurDataURL
, blurHeight
, blurWidth
)을 확인할 수 있다. 이들은 placeholder
에서 사용되며 이미지가 로드되기 전 blurry(뿌연)한 효과를 보여준다.
import Image, { StaticImageData } from 'next/image';
import cat from '@/public/cat.jpg';
export default function Home() {
return (
<>
<Image
src={cat}
alt="cat"
sizes="100vw"
width={500}
placeholder="blur" // placeholder 효과
/>
<div className="p-4">
<p>src : {cat.src}</p>
<p>height : {cat.height}</p>
<p>width : {cat.width}</p>
<p>blurDataURL : {cat.blurDataURL}</p>
<p>blurHeight : {cat.blurHeight}</p>
<p>blurWidth : {cat.blurWidth}</p>
</div>
</>
);
}
svg + xml 형식의 이미지가 blurDataURL
이미지이며 width가 8, 이미지 크기가 1.1kB인 매우 저해상도의 이미지다.
원격 이미지를 가져올 경우 루트 디렉토리에 있는 next.config.js
파일에서 설정이 필요함
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.unsplash.com', // 이미지의 출처를 표시
},
],
},
};
import Image from 'next/image';
export default function Home() {
return (
<Image
src="https://images.unsplash.com/photo-1472491235688-bdc81a63246e?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="cat"
sizes="100vw"
width={500}
placeholder="blur"
/>
);
}
예상 되는 에러는??
height
속성도 넣어줘야한다.import Image from 'next/image';
export default function Home() {
return (
<Image
src="https://images.unsplash.com/photo-1472491235688-bdc81a63246e?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="cat"
sizes="100vw"
width={500}
height={333}
placeholder="blur"
/>
);
}
예상 되는 에러는??
blurDataURL
가 자동으로 생성되지 않는다.placeholder
를 제거하면 blur 로딩 효과가 없어지고 에러도 사라진다.원격 외부 이미지인데 loading 효과를 만들고 싶다면?
import Image from 'next/image';
export default function Home() {
return (
<Image
src="https://images.unsplash.com/photo-1472491235688-bdc81a63246e?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="cat"
sizes="100vw"
width={500}
height={333}
placeholder="blur"
blurDataURL="/blur.png" // 그냥 회색의 로컬 이미지
/>
);
}
구리긴 한데 없는 것보단 낫다.
고정된 이미지가 아니라 로컬이미지와 같이 동적인 blurry placeholder를 사용하고 싶다면?
import Image from 'next/image';
import lqip from 'lqip-modern'; // 외부 라이브러리 설치
export default async function Home() {
// 서버컴포넌트로
const url =
'https://images.unsplash.com/photo-1472491235688-bdc81a63246e?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D';
const imgData = await fetch(url);
const arrayBufferData = await imgData.arrayBuffer();
const lqipData = await lqip(Buffer.from(arrayBufferData));
return (
<Image
alt="cat"
placeholder="blur"
src={url}
width={500}
height={333}
blurDataURL={lqipData.metadata.dataURIBase64}
/>
);
}
import Image from 'next/image';
export default async function Home() {
return (
<div className="flex">
// 세로가 긴 이미지
<Image
alt="cat"
src="https://images.unsplash.com/photo-1533738363-b7f9aef128ce?q=80&w=2535&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
width={500} // 가로 세로 너비를 모두 500px로
height={500}
/>
// 가로가 긴 이미지
<Image
alt="cat"
src="https://images.unsplash.com/photo-1472491235688-bdc81a63246e?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
width={500} // 가로 세로 너비를 모두 500px로
height={500}
/>
</div>
);
}
div
태그의 flex로 인해 두번째 이미지의 비율이 깨지게 된다.width
와 height
를 모두 500px로 했지만 실제 이미지는 1:1 비율이 아니므로 height={500}
이 무시된다.fill
이다.import Image from 'next/image';
export default async function Home() {
return (
<div className="flex">
<div className="w-[500px] h-[500px] relative"> // position relative, 부모요소에만 width & height
<Image
fill
alt="cat"
src="https://images.unsplash.com/photo-1533738363-b7f9aef128ce?q=80&w=2535&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
/>
</div>
<div className="w-[500px] h-[500px] relative">
<Image
fill
alt="cat"
src="https://images.unsplash.com/photo-1472491235688-bdc81a63246e?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
/>
</div>
</div>
);
}
fill
을 사용하게 되면 부모요소에 가득 차게 된다.
이 때 부모요소는 반드시 css position이 relative
or absolute
or fixed
이여야 한다.
이미지는 부모요소에 따라 가로, 세로길이가 결정되기 때문에 width
와 height
속성을 넣으면 안된다(넣으면 에러남).
그러나 이렇게 하면 강제적으로 사진의 비율이 1 : 1(500 : 500)이 된다. 이는 css object fit
속성을 cover
로 해주면 비율을 유지한채 부모요소를 가득 채우게 된다. 경우에 따라 contain
이나 다른 값을 사용 할 수 있다.
import Image from 'next/image';
export default async function Home() {
return (
<div className="flex">
<div className="w-[500px] h-[500px] relative">
<Image
fill
alt="cat"
src="https://images.unsplash.com/photo-1533738363-b7f9aef128ce?q=80&w=2535&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
className="object-cover" // CSS | object-fit: cover와 동일
/>
</div>
<div className="w-[500px] h-[500px] relative">
<Image
fill
alt="cat"
src="https://images.unsplash.com/photo-1472491235688-bdc81a63246e?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
className="object-cover" // CSS | object-fit: cover와 동일
/>
</div>
</div>
);
}
추가적으로 fill
속성은 정적 로컬이미지에서도 사용가능하다.
fill
속성을 사용하면 width
와 height
가 없으므로 CLS가 발생하지 않을까?
srcset
과 더불어 반응형 이미지 최적화 구현을 위해 꼭 필요한 속성이다.srcset
과 size
에 관한 내용은 mdn을 참고하시길 바랍니다.import Image from 'next/image';
import cat from '@/public/cat.jpg';
export default async function Home() {
return (
<>
<Image alt="cat" src={cat} /> // size 속성 없음
<Image alt="cat" src={cat} sizes="100vw" /> // size 속성 있음
</>
);
}
size
값을 주지 않은 <Image alt="cat" src={cat} />
의 경우와 다르게
<Image alt="cat" src={cat} sizes="100vw" />
의 경우 next가 알아서 다양한 width의 image srcset을 제공해준다.
size
값의 계산이 간단한 경우라면 쉽게 설정해줄 수 있다.
<Image alt="cat" src={cat} width={500} />
같은 경우 화면의 너비가 500이하로 줄어들 경우 사진은 기기 너비의 100%를 차지하게되며 화면의 너비가 500보다 크다면 사진의 width는 500으로 일정하다.
이를 적용하면 아래와 같다.
import Image from 'next/image';
import cat from '@/public/cat.jpg';
export default async function Home() {
return (
<Image
alt="cat"
src={cat}
width={500}
sizes="(max-width: 500px) 100vw, 500px"
/>
);
}
반응형은 항상 머리를 아프게 한다. 따라서 이를 대신해주는 웹사이트(?) 기능(?)을 소개한다. https://ausi.github.io/respimagelint/
size
값을 일부러 잘못 입력한 다음 북마크로 추가한 Lint Images를 누르면 된다.import Image from 'next/image';
import cat from '@/public/cat.jpg';
export default async function Home() {
return <Image alt="cat" src={cat} width={500} sizes="10vw" />; // 일부러 잘못된 "10vh" 입력
}
정확한 값은 아니지만 얼추 비슷하게 나온다.
해당 기능은 아래과 같이 복잡한 size를 설정해야하는 경우에 매우 유용하다.
import Image from 'next/image';
import cat1 from '@/public/cat1.jpg';
import cat2 from '@/public/cat2.jpg';
import cat3 from '@/public/cat3.jpg';
import cat4 from '@/public/cat4.jpg';
import cat5 from '@/public/cat5.jpg';
import cat6 from '@/public/cat6.jpg';
import cat7 from '@/public/cat7.jpg';
export default async function Home() {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
// md(tailwind에서 설정한 break point) 이상이면 grid column이 3줄, sm이상이면 grid column이 2줄, 그 이하는 1줄
<Image alt="cat" src={cat1} width={500} />
<Image alt="cat" src={cat2} width={500} />
<Image alt="cat" src={cat3} width={500} />
<Image alt="cat" src={cat4} width={500} />
<Image alt="cat" src={cat5} width={500} />
<Image alt="cat" src={cat6} width={500} />
<Image alt="cat" src={cat7} width={500} />
</div>
);
}
import Image from 'next/image';
import cat1 from '@/public/cat1.jpg';
import cat2 from '@/public/cat2.jpg';
import cat3 from '@/public/cat3.jpg';
import cat4 from '@/public/cat4.jpg';
import cat5 from '@/public/cat5.jpg';
import cat6 from '@/public/cat6.jpg';
import cat7 from '@/public/cat7.jpg';
export default async function Home() {
return (
<div className="flex flex-col gap-4">
<Image alt="cat" src={cat1} width={500} placeholder="blur" priority />
<Image alt="cat" src={cat2} width={500} placeholder="blur" priority />
<Image alt="cat" src={cat3} width={500} placeholder="blur" priority />
<Image alt="cat" src={cat4} width={500} placeholder="blur" />
<Image alt="cat" src={cat5} width={500} placeholder="blur" />
<Image alt="cat" src={cat6} width={500} placeholder="blur" />
<Image alt="cat" src={cat7} width={500} placeholder="blur" />
</div>
);
}
fetchPriority
가 high
로 바뀐 것을 확인 할 수 있다.