회사에서 업무를 보던 중에 동료 디자이너분이 오시더니 이미지 화질이 좋지않고 이미지 로딩이 늦어진다 라는 말을 듣게 되었다. 사실 확인을 위해 개발하고 있던 부분을 멈추고 해당 이슈 부분을 다시 보니 정말로 화질도 안좋고, 렌더링 속도가 생각보다 늦어지는 현상을 볼 수 있었다... 나는 단순히 Next에서 제공하는 Image 태그를 사용해서 배포하면 운영서버에서 알아서 잘 최적화(?) 해주겠지 라는 생각을 가지고 있었기 때문에 이러한 부분을 놓치고야 말았다. 그래서 좀 더 자세하게 공부를 하였고 해당 이슈를 어떻게 해결했는지에 대해 정리하고자 한다.
NEXT.JS Version은 12.3.4를 사용하고 있다.
The Next.js Image component extends the HTML img element with features for automatic image optimization
next.js에서 지원해주는 Image 요소는 자동 이미지 최적화를 위한 기능으로 HTML img 태그를 확장한 부분이라고 하였고, 이미지 최적화를 위해 아래와 같이 4가지 기능을 지원하고 있다.
기본적으로 Next.js에서 제공하는 Image는 위에 설명하였듯이 자동으로 이미지 최적화를 하기 때문에 아래 코드와 같이 작성해주었고 외부 URL(Remote image)을 사용할 경우에는 Next.js에서 빌드 중에 해당 이미지에 접근할 수 없으므로 Next.js 입장에서 해당 image에 대한 정보를 알 수 없으니 width와 height를 직접 입력을 꼭 해야한다. 만약 입력을 하지 않으면 error가 발생하기 때문에 조심해야 한다!
반면에 내부 Url(Local image)는 로컬에 존재하기 때문에 width와 height을 입력하지 않아도 next.js는 해당 image에 대한 정보를 알수 있다.
// Remote image
<Image
src="AWS S3 Remote url"
alt="대표 이미지"
width={500}
height={500} />
// Local image
<Image
src="local url"
alt="대표 이미지" />
// 기존 코드
<Image
src={imagePath}
width={100}
height={112}
alt="미션 이미지"
className="mission-item__image"
/>
// 개선된 코드
<Image
src={imagePath}
alt="미션 이미지"
layout="fill"
/>
위 코드를 보았을때 개선된 코드에서 width와 height 값이 사라진 부분과 layout="fill" 이라는 option을 추가한 부분을 볼 수 있다. 먼저! layout에 대한 개념을 살펴보자
layout 속성은 Image 요소를 사용했을 때 이미지가 어떤 형태로 보여주어야 한다는 정의를 가지고 있다. next.js 13 버전 이후로는 layout="intrinsic" 값이 default로 사용되지만 내가 사용하고 있는 12.3.4 버전 같은 경우에는 default값이 없어 상황에 맞게 layout 속성을 사용해야한다!
intrinsic
원본 이미지 크기로 렌더링하고 화면의 크기에 맞춰 자동으로resizing
된다.
fixed
화면을 줄이든 늘리든 고정할 수 있도록 한다.
responsive
화면 크기에 맞춰 width값이 계속 늘어난다.
fill
원본 이미지의 사이즈를 모를 때 사용한다(부모 요소 크기 설정 해야함)
부모 요소에 position: relative 꼭 넣어줘야 한다!!!!
아래의 적용 예시를 보면 좀 더 이해가 잘 될거다!
next가 이미지를 최적화 할 때, 원본 이미지를 여러 srcset으로 쪼개어서 최적화 해준다. 예를 들어 원본 이미지가 670px이라 하더라도 next 서버에는 128px, 256px 등 다양한 사이즈의 이미지가 저장되어 있고, 그 후 상황에 맞게 적절한 크기의 이미지를 내려준다. 따라서 원본 이미지가 670px이라 하더라도 실제로 사용되는 이미지는 원본과 다를 수 있어 화질에도 문제가 생긴다는 부분을 알 수 있었다.
원본이미지 사이즈를 모르기 때문에 아래와 같이 부모 div의 렌더링 할 이미지의 사이즈인 width와 height 값을 주고 Image 요소에 layout="fill" 을 넣어주면 부모 태그의 widht와 height에 맞추기 위해 Image 요소가 자동적으로 조절을 하기 때문에 내가 사용하려는 원본 이미지 그대로 사용할 수 있다.
<div className='mission-item__image-box'>
<Image
src={imagePath}
alt="미션 이미지"
layout="fill"
className="mission-item__image"
/>
</div>
// css
.mission-item__image-box {
position: relative
width: 100px;
height: 112px;
}
Next에서 제공하는 Image 요소는 기본적으로 lazy loading을 지원한다고 알고 있다. 하지만 lazy loading이 적용 되더라도 이미지 용량이 큰애들은 렌더링 후에 1~2초 씩 늦게 로딩이 되어서 서비스에 안좋은 영향을 끼치고 있었다.
첫번째 해결 방법
첫번째 방법으로는 priority 속성을 넣어주는 방법이다.
priority를 넣어주게 되면 이미지 로딩이 높은 우선순위를 가진다는 의미이고, LCP(Largest Contentful Paint)요소로 감지된 이미지에는 priority 속성을 사용해야 한다. 결과적으로 대부분의 경우 priority로 해결이 되었지만 파일 용량이 큰 이미지는 만족할만한 성과를 거두지 못해서두번째 방법을 사용해보았다.
<div className='mission-item__image-box'>
<Image
src={imagePath}
alt="미션 이미지"
layout="fill"
priority
className="mission-item__image"
/>
</div>
두번째 해결 방법
두번째 방법은 placeholder="blur" 속성을 넣어주는 방법이다.
placeholder 속성을 넣어주게 되면 이미지가 로드되기 전까지 blur 처리된 이미지를 보여주는 방식이다.
placeholder 속성에는 기본적으로 'empty', 'blur'가 있는데 'blur'를 넣어주게 되면 로드되기 전까지 다른 이미지를 보여주어야 할
blurDataURL를 추가로 넣어주어야 한다.
아래 코드와 실제로 어떻게 화면에 나오는지 보면 바로 이해 가능하다!
추가적으로 blurDataURL에 들어갈 url값은 base64로 인코딩된 값만 들어 갈 수 있다.
next 공식문서에 따르면 아주 작은 이미지(10px 이하)를 권장하기 때문에 인코딩을 된다고 한다.
https://nextjs.org/docs/pages/api-reference/components/image#blurdataurl
<div className='mission-item__image-box'>
<Image
src={imagePath}
alt="미션 이미지"
layout="fill"
placeholder="blur"
blurDataURL="base64로 인코딩된 url"
className="mission-item__image"
/>
</div>
지금 다니고 있는 회사에서 본격적으로 Next를 사용하고 있는데, 생각보다 모르는게 너무 많아서 헷갈리는 부분이나 새롭게 접하는 부분이 있을 때 정리하면 좋을 것 같다고 생각했다.
https://nextjs.org/docs/pages/api-reference/components/image-legacy
잘 봤습니다. 좋은 글 감사합니다.