본문 바로가기
project/withMe

NEXT/IMAGE를 활용한 웹 성능 최적화

by qjatjs123123 2025. 2. 27.

안녕하세요!

 

withMe 서비스의 프론트엔드는 NEXT.JS 프레임워크를 사용하여 웹 페이지를 개발하고 있습니다.

 

이번에는 Next/Image를 활용한 이미지 관련 웹 성능 최적화 경험을 공유하고자 합니다.

CLS, LazyLoading, 이미지 프로세싱 순으로 다룰 예정입니다.

 

많은 관심과 피드백 부탁드립니다. 🙏

 

 


 

 

서론

CLS(Cumulative Layout Shift)란?

 

사용자가 예상하지 못한 레이아웃 이동이 발생한 정도를 측정한 값입니다.

 

 

위 예시를 보게 되면 사용자가 버튼을 클릭하려고 한다고 가정해 봅시다. 클릭하려는 순간 이미지가 추가로 렌더링이 이루어지고 기존 클릭버튼 자리를 이미지가 대체하고 있습니다. 결과적으로 사용자는 버튼이 아닌 이미지를 클릭하고 있네요

 

위 예시는 단순히 버튼 클릭하는 것이기에 조금의 불편함 정도지만 결제, 환불 등과 같은 사용자의 선택이 중요한 경우에는 문제가 커질 수 있습니다.

 

구글에서 CLS를 하나의 핵심 웹 바이탈 측정 지표 중 하나로 간주하고 있습니다.

 

그럼 언제 CLS가 발생할까요?

 

주로 동적으로 Import되는 리소스들로 인해 발생합니다.

  • 차수가 없는 이미지
  • 동적으로 삽입된 콘텐츠
  • 웹 글꼴

등이 있습니다.

 

저는 여기서 차수가 없는 이미지를 Next/Image를 활용하여 CLS 문제를 해결하는 방법에 대해 공유하고자 합니다.

 

 


 

Next/Image

먼저 Next/Image 컴포넌트가 무엇인지 알아보겠습니다.

Next/Image 컴포넌트는 이미지를 최적화하고 성능을 향상시키는 컴포넌트입니다. Next/Image 컴포넌트를 사용하면 이미지를 자동으로 최적화하고, 브라우저에 필요한 이미지 사이즈만큼만 로딩합니다.

 

먼저 Next/Image 컴포넌트에 필수 속성이 있습니다.

  • src : 이미지 경로
  • width : 이미지의 가로 크기
  • height : 이미지 세로 크기
  • alt : 이미지 대체 텍스트

width와 height를 지정하면 이미지가 로딩되기 전에 해당 크기만큼의 공간을 미리 차지하게 됩니다. 이미지가 로드되면 지정된 크기에 맞춰 해당 영역에 렌더링됩니다. 

 

 

변하지 않는 이미지는 실제 픽셀 단위로 width와 height를 지정해도 큰 문제가 없습니다. 하지만 반응형 이미지의 경우, 화면 크기에 맞게 자동으로 크기가 조정되도록 설정해야 합니다. 이때는 fill 또는 responsive 속성을 사용합니다.

 

 

 

간단한 코드로 살펴볼까요?

(fill)

<div style={{ position: 'relative', width: '100%', height: '500px' }}>
  <Image 
    src="/path/to/image.jpg" 
    alt="Fill Image" 
    layout="fill" 
    objectFit="cover"
  />
</div>

 

fill 속성은 이미지가 부모 컨테이너의 전체 영역을 채우도록 합니다. 또한 width, height를 정의할 필요가 없어집니다. 이 속성을 사용하기 위해선 부모에 relative 속성을 꼭 주셔야합니다. 그래야지 img가 absolute로 부모 전체 영역을 채울 수 있습니다. 이미지는 부모 컨테이너에 맞춰 크기가 바뀌기 때문에, 이미지 비율이 변할 수 있습니다. 

 

그래서 object-fit: contain을 사용하여 이미지 비율을 유지하면서 부모 컨테이너에 맞게 이미지를 표시하거나, 부모 컨테이너의 비율을 이미지 비율에 맞게 조정하는 방법을 고려해야 합니다.

 

 

 

(responsive)

<div style={{ position: 'relative', width: '100%', height: '500px' }}>
  <Image 
      src="/path/to/image.jpg" 
      alt="Responsive Image" 
      width={1200} 
      height={800} 
      layout="responsive"
   />
</div>

 

responsive는 fill과 다르게 이미지가 부모 컨테이너의 크기에 맞춰 반응형으로 조정됩니다. 이때, 이미지는 원본 비율(예: 1200x800)대로 크기가 조정되며, 부모 요소에 맞춰 크기가 자동으로 변합니다. 즉, 부모 영역을 전체로 채우지는 않고, 부모 컨테이너의 크기에 맞춰 비율을 유지하면서 크기가 변하게 됩니다.

 

여기서 width와 height는 비율을 적어주시면 됩니다. 1200x800 대신 12x8도 가능합니다.

 

 

이 두 속성을 사용하면 반응형 디자인에서 이미지 크기를 미리 정의하여 CLS (Cumulative Layout Shift) 문제를 해결할 수 있습니다.

 

저는 여기서 fill 속성을 사용하였습니다.

<div className="relative flex-1  mt-[30px] flex justify-center w-full items-center" 
                style={{paddingBottom:'47.83%', marginTop:'30px', width:'100%'}}>        
    <Image
      className="image"
      src={MainImg1}
      alt="Main logo of the image"
      sizes="100%"
      layout='fill'
      objectFit="contain"
    />
</div>

 

fill 이니까 부모 크기 만큼 영역을 차지하겠죠? 

그리고 부모 요소에 relative를 설정한 후, padding-bottom을 사용하여 이미지 비율만큼 부모 요소의 영역을 정의하였습니다.

그러면 이미지는 비율깨짐이 없이 정확한 크기를 차지할 수 있습니다. 

또한 contain을 주어 확실히 이미지 비율을 유지하도록 하였습니다.

 

 


 

 

결과

 

 

 


 

 

웹 성능 최적화

1. LazyLoading

 

웹 리소스 중 가장 많은 비중을 차지하는 이미지를 어떻게 처리하느냐는 매우 중요한 문제입니다.

웹 페이지의 모든 컨텐츠를 로드한다면 상당히 오랜 시간이 걸립니다.

만약 사용자가 최상단의 이미지만 확인하고 나가버리면 어떨까요?

 

비효율적입니다.

그래서 Next/Image는 LazyLoading을 제공합니다.

 

 

LazyLoading이란 페이지를 불러오는 시점에 당장 필요하지 않는 리소스는 추후에 로딩하는 기술입니다.

즉 사용자가 보고있는 뷰포트만 불러오고 보이지 않는 것들은 당장 로딩하지 않는 것이죠

 

 

Next/Image는 기본적으로 LazyLoading이 활성화 되어있습니다.따로 설정하지 않아도 LazyLoading이 동작합니다.

 

 

 

2. webp 변환

리소스의 크기가 클수록 그만큼 느리게 로드됩니다. 따라서 최대한 리소스 크기를 줄여야 합니다.

Next/Image는 이미지 파일을 webp로 압축되어 크기를 줄여줍니다.

 

또한 리소스들을 캐시처리도 지원하구요

 

 

 

3. 이미지 리사이즈

만약 렌더링에 필요한 크기는 500x500인데 가져오는 크기는 1500x1500이라면 불필요한 리소스가 사용됩니다.

이러한 문제를 해결하기 위해 Next/Image는 이미지 리사이즈 기능을 지원합니다.

 

Next/Image 컴포넌트 옵션 속성 중 sizes가 있습니다.

앞서 width, height로 CLS 문제를 해결했다면 sizes로 리사이즈 문제를 해결할 수 있습니다.

 

 

코드로 살펴봅시다.

저는 grid 속성을 사용하여 Item들을 반응형으로 만들었습니다.

//CSS
.grid_mainGrid {
  display: grid;
  align-items: stretch;
  grid-gap: 32px;
  padding: 0;
  margin: 0;
  --card-count: 4;
  --spacer: calc(var(--card-count) - 1);
  --width: 25%;

  @media screen and (max-width: 1440px) {
    --card-count: 3;
    --width: 33.33%;
  }

  @media screen and (max-width: 1056px) {
    --card-count: 2;
    --width: 50%;
  }

  @media screen and (max-width: 768px) {
    grid-template-columns: repeat(1, 100%);
    grid-gap: 16px;
  }
}


<Image
  className="workspace-image cursor-pointer"
  src={workspace.thumbnail}
  alt="Description of the image"
  layout="fill" // ✅ fill 사용
  objectFit="cover" // ✅ 부모 크기에 맞춰 자동 조정
  sizes="(max-width: 768px) 100vw, (max-width: 1056px) 50vw, (max-width: 1440px) 33.33vw, 25vw"
  style={{
    border: '1px solid #eeeeee',
  }}
/>

 

Image는 전체 크기를 지정할 필요가 없습니다.

Grid에서 설정한 크기에 맞춰 자동으로 리사이즈되므로, sizes 속성에 따라 뷰포트 크기에 맞게 유동적으로 조정됩니다.

  • 768px 이하에서는 100vw
  • 1056px 이하에서는 50vw
  • 1440px 이하에서는 33.33vw
  • 그보다 클 경우 25vw

이렇게 설정하면 이미지 리사이즈를 하여 성능을 최적화 할 수 있습니다.

 

 


 

 

마무리

Next/Image를 잘 사용하면 웹 성능 최적화를 할 수 있습니다.

하지만 Next 서버가 그만큼 부하가 많이 걸린다는 단점이 있습니다.

이를 해결하기 위해 CDN서버를 활용하여 분산 처리하면 성능을 더욱 향상시킬 수 있습니다.