NextJS
에서Image
컴포넌트를 제공하는 것을 모르고img
태그를 썼는데 아래와 같이 배포 과정에서 warning 메시지가 떴다. 그래서img
태그를Image
컴포넌트로 바꾸었고 그 과정에서 공부한 내용들을 정리해보려고 한다.
👀 NextJS 공식문서
Image 컴포넌트는 img 태그의 확장이다. 공식문서에 따르면, 우수한 Core Web Vitals를 달성하기 위해 Image 컴포넌트에 기본으로 최적화 기능이 포함되어 있다. 이때 Core Web Vitals은 사용자 경험을 측정하는 중요한 척도이고, Google 검색 순위에 반영이 된다.
Image 컴포넌트의 장점 👍
Improved Performance
: 최신 이미지 형식을 사용하여 언제나 디바이스 사이즈에 맞게 최적화된 이미지를 제공한다.Visual Stability
: Cumulative Layout Shift (CLS)를 자동으로 방지해준다.Faster Page Loads
: 이미지가 뷰포트에 들어왔을 때만 로드되기 때문에 초기 페이지 로드 속도가 빠르다.Asset Flexibility
: 외부에 저장되어 있는 이미지까지도 리사이징이 가능하다.Image 컴포넌트를 사용하면 img 태그를 사용했을 때보다 사용자 경험이 향상이 되고 기본으로 최적화 기능을 제공하니 img 태그 대신 Image 컴포넌트를 사용하기로 결정했다.
src
1. 정적으로 불러온 이미지 파일
2. 외부 이미지 경로
이때 외부 이미지 경로를 사용할 때는
next.config.js
에서domains
설정을 해야 한다.const module.exports = { images: { domains: ["image.tmdb.org"], }, };
width & height
CLS
를 방지하기 위해서이다.layout
fixed
: 지정된 width와 height 값을 유지한다.fill
: 이미지가 부모 요소 크기만큼 늘어난다. 이때 부모 요소의 position 값이 relative이여야 한다.placeholder ❣️
empty
(기본) : 대체 이미지가 없다.blur
: blurDataURL
props 값이 대체 이미지로 사용된다.src
가 정적으로 불러온 이미지이고 이 이미지가 .jpg, .png, .webp, .avif라면 blurDataURL이 자동으로 채워진다. (따로 blurDataURL props 값을 전달할 필요 없다.)src
가 동적으로 불러오는 이미지라면 blurDataURL props 값을 반드시 전달해야 한다. blurDataURL
placeholder="blur"
를 사용할 때 함께 이 props를 사용한다.objectFit
layout="fill"
를 사용할 때 이미지가 부모 요소에 어떻게 채워질 것인지 정의한다.object-fit
CSS 속성값을 전달한다.Image
컴포넌트를 사용하면서 가장 어려웠던 점은 width
와 height
를 설정하는 것이었다.
width에 맞는 비율로 height를 auto
값으로 줘야 하는데 Image
컴포넌트는 두 값 모두 필수이다. 😭
방법을 찾다가 아래 글을 읽었다.
NextJS Image 태그 height auto로 사용하기
import Image, { ImageProps } from "next/image"; const AutoHeightImageWrapper = styled.div` width: 100%; & > span { position: unset !important; img { height: auto !important; position: relative !important; } } `; const AutoHeightImage = ({ ...props } : ImageProps) => { <AutoHeightImageWrapper> <Image layout="fill" {...props} /> </AutoHeightImageWrapper> } export default AutoHeightImage;
위 코드를 응용하여 height
값이 auto
인 PosterImage
컴포넌트를 구현했다!
import Image from "next/image";
import styled from "styled-components";
const Img = styled(Image)`
height: auto !important;
position: relative !important;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
border-radius: 15px;
`;
const PosterImage = ({ src }: { src: string }) => {
return (
<Img
src={`https://image.tmdb.org/t/p/w500/${src}`}
layout="fill"
placeholder="blur"
blurDataURL="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnt2PQAF5AJMrzp1XwAAAABJRU5ErkJggg=="
/>
);
};
export default PosterImage;
// PosterImage 사용하기 (1)
import PosterImage from "./common/PosterImage";
import { IMovieProps } from "../lib/api/movies";
import styled from "styled-components";
const ImageWrapper = styled.div`
width: 100%;
position: relative;
& > span {
position: unset !important;
}
`;
const Movie = ({ movie }: { movie: IMovieProps }) => {
return (
<ImageWrapper>
<PosterImage src={movie.poster_path} />
</ImageWrapper>
);
};
export default Movie;
// PosterImage 사용하기 (2)
import PosterImage from "./common/PosterImage";
import { IMovieProps } from "../lib/api/movies";
import styled from "styled-components";
const ImageWrapper = styled.div`
width: 300px;
position: relative;
& > span {
position: unset !important;
width: 300px !important;
}
@media ${({ theme }) => theme.device.mobile} {
width: 100%;
& > span {
width: 100% !important;
}
}
`;
const Movie = ({ movie }: { movie: IMovieProps }) => {
return (
<ImageWrapper>
<PosterImage src={movie.poster_path} />
</ImageWrapper>
);
};
export default Movie;