웹 페이지에서 이미지는 가장 큰 용량을 차지하는 요소 중 하나입니다. 따라서, 이미지 크기를 최적화하면 로드 해야 할 리소스 용량을 절감하여 성능 개선이 가능합니다.
| 파일 유형 | 총 크기 (KB) | 비율 (%) |
|---|---|---|
| 이미지 (image) | 343 | 67 |
| 자바스크립트 (script) | 111 | 22 |
| 기타 (other) | 31 | 6 |
| 스타일시트 (stylesheet) | 23 | 5 |


| 파일 유형 | 총 크기 (KB) | 비율 (%) |
|---|---|---|
| image | 450 | 54 |
| script | 320 | 38 |
| stylesheet | 50 | 6 |
| other | 15 | 2 |
차세대 이미지 형식인 WebP와 AVIF를 사용하면 이미지 품질을 유지하면서 파일 크기를 크게 줄일 수 있습니다.
이미지를 실제 표시될 크기에 맞게 조정하여 불필요한 데이터 전송을 방지합니다.
CLS(Cumulative Layout Shift)는 페이지의 시각적 안정성을 측정하는 지표로, 레이아웃 이동을 최소화하는 것이 중요합니다.
사용자가 실제로 이미지를 볼 때까지 로딩을 지연시켜 초기 로딩 속도를 향상시킵니다.
중요한 이미지를 미리 로드하여 사용자에게 빠른 콘텐츠 표시를 제공합니다.
1-1. Next.js 에서 WebP 형식 사용
Coworkers 는 Next.js 프로젝트입니다.
Next.js 는 next/image 컴포넌트를 사용하여 자동으로 WebP 와 같은 차세대 이미지 형식을 지원하여 이미지 크기를 줄입니다.
next/image컴포넌트는 config 에 formats 가 제공되지 않으면 image/webp 형식이 기본값이 됩니다. image/avif 를 formats 배열 앞 순서에 사용하면 AVIF 지원을 활성화할 수 있습니다.https://nextjs-ko.org/docs/pages/api-reference/components/image#formats

위와 같이 next/image 컴포넌트를 사용한 경우, webp 형식으로 파일을 로드하는 것을 확인할 수 있습니다.
1-2. Next.js 에서 SVG 형식 사용

여기서 Member.svg 나 Thumbnail_team.svg 는 svg+xml 형식으로 로드해 상대적으로 크기가 크고 로드 시간이 오래 걸리는 것을 확인할 수 있습니다.
Next.js 는 기본적으로 SVG 이미지를 최적화하지 않습니다.
기본 로더는 몇 가지 이유로 SVG 이미지를 최적화하지 않습니다.
첫째, SVG는 크기를 조정해도 손실이 없는 벡터 형식입니다.
둘째, SVG는 HTML/CSS와 동일한 기능을 많이 가지므로 적절한 콘텐츠 보안 정책(CSP) 헤더 없이도 취약성을 초래할 수 있습니다.
https://nextjs-ko.org/docs/pages/api-reference/components/image#dangerouslyallowsvg
SVG 를 사용하는 이유
SVG 는 2차원 벡터 그래픽을 서술하는 XML 기반의 마크업 언어입니다.
해상도에 관계 없이 모든 사이즈에서 선명하게 렌더링되는 이미지를 제공합니다.
다양한 디바이스와 화면 크기에서 이미지 품질을 유지하기 위해 사용합니다.
SVG 는 XML 기반이기 때문에 CSS, JavaScript 를 사용하여 이미지 스타일 변경이나 애니메이션 적용을 할 수 있습니다.
SVG 를 React 컴포넌트로 import 해서 재사용성을 높이고 모듈화를 할 수 있습니다.
Next.js 는 React 기반이지만 React 에서 사용하는 방식으로 svg 를 읽으면 읽을 수 없다는 에러가 뜹니다. svgr 을 사용하여 컴포넌트 형식으로 사용할 수 있습니다.
SVGR 사용
SVGR 은 Next.js 를 비롯해 Remix, Node.js 등에서 SVG 를 React 컴포넌트로 변환해줍니다.
React 컴포넌트로 만들면 사용하기 편리해지고 재사용성을 높일 수 있습니다.
변환 과정에는 아래와 같은 것들이 있습니다.
1. SVGO 라는 SVG 최적화 도구, Node.js 라이브러리를 사용하여 최적화
2. 여러 단계로 HTML 을 JSX 로 변환
3. React 컴포넌트에 JSX 래핑
4. Babel AST 를 코드로 변환
5. Prettier 를 사용하여 코드 포맷팅
아래 링크에서 Next.js 에서 svgr 을 사용하는 방법을 확인할 수 있습니다.
https://react-svgr.com/docs/next/

위 네트워크 리소스 파일은 Thumbname_team.svg 파일을 React 컴포넌트로 변환하여 사용하는 SVG 파일 모듈입니다. next/image 사용에서 svgr 를 활용한 방법으로 수정한 결과 파일 크기는 훨씬 커지고(1.5kB → 258kB) 그에 따라 로드 시간이 늘어난 것(3밀리초 → 34 밀리초)을 확인했습니다.
종합하면, SVG 파일은 next/image 를 사용하는 것이 최적화가 가장 잘 되며, 스크립트를 이용한 스타일 변경이나 재사용성을 높이기 위해 svgr 을 사용해야 한다면 필수적인 이미지만 svg 로 사용해야 효율적일 것입니다.
Thumbail_team 파일은 svg 로 활용해야 할 이유가 없기 때문에 webp 로 변환하여 사용하는 것이 가장 좋은 방법입니다.


Sqoosh(GoogleChromeLabs 의 이미지 압축 웹앱) 를 통해 Thumbail_team.svg 를 webp 로 변환한 결과 품질을 100으로 설정해도 크기가 61%(3.9kB → 1.5kB) 줄어든 것을 확인했습니다.
물론 앞에서 설명했듯이 Squoosh 나 cwebp 를 사용하지 않고, 다른 형식을 사용해도 Next.js 에서 next/image 컴포넌트를 사용하면 자동으로 WebP 형식을 지원합니다.

svg 에서 webp 로 변환된 결과 기존 1.5kB 에서 1.0kB 로 크기가 줄어든 것을 확인했습니다.
팀 페이지 내 모든 불필요한 svg 파일에 적용한 결과 FCP(First Contentful Paint) 55% 단축(0.9초 → 0.5초), LCP(Largest Contentful Paint) 45% 단축(1.3초 → 0.6초)하는 결과를 얻을 수 있었습니다.


FCP(First Contentful Paint)
페이지 로드가 시작된 시점부터 페이지 콘텐츠의 일부가 화면에 렌더링되는 시점까지의 시간을 측정합니다.
LCP(Largest Contentful Paint)
페이지의 주요 콘텐츠가 로드되었을 가능성이 있는 페이지 로드 타임라인의 지점을 표시합니다.
적절한 크기의 이미지를 사용한다는 것은 이미지를 실제 표시될 크기에 맞게 조정하여 불필요한 데이터 전송을 방지한다는 것을 의미합니다.
반응형 이미지를 처리하기 위해서 뷰포트의 크기부터 사용자 화면의 해상도 등 많은 환경을 고려해야 합니다.
하지만 HTML img 의 srcset 과 sizes 를 통해 쉽게는 이미지의 크기를 설정하는 것만으로 대부분의 고려 사항을 사용자 브라우저에 맡길 수 있습니다.
srcset
srcset 은 브라우저에서 선택할 수 있는 이미지 세트와 각 이미지 크기를 정의합니다.
sizes
sizes 는 일련의 미디어 조건(ex: 화면 너비)을 정의하고 특정 미디어 조건에 해당할 때 어떤 이미지 크기를 선택하는 것이 가장 좋을지 알려줍니다.
브라우저 동작
1. 기기 너비를 확인합니다.
2. sizes 목록에서 어떤 미디어 조건이 가장 먼저 참인지 알아냅니다.
3. 해당 미디어 쿼리에 지정된 슬롯 크기를 확인합니다.
4. 슬롯과 크기가 같은 srcset 목록에 참조된 이미지 또는 이미지가 없는 경우 선택한 슬롯 크기보다 큰 첫 번째 이미지를 로드합니다.
https://developer.mozilla.org/ko/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images
Next.js 의 next/image 컴포넌트도 sizes 속성을 제공하는데, 성능과 관련된 두 가지 동작을 수행합니다.
sizes 값을 통해 next/image 에서 자동 생성한 srcset 에서 다운로드할 이미지 크기를 브라우저가 결정합니다.sizes 값이 없으면 고정 크기 이미지( 1x / 2x / 등 ) 에 적합한 srcset 이 생성되고, 정의된 경우엔 반응형 이미지(640w/750w/등) 에 적합한 srcset 이 생성됩니다.Next.js 에서 제공하는 next/Image 컴포넌트를 활용하여 반응형 이미지를 만드는 방법은 다음과 같습니다.
width 와 heigth 를 제공해야 합니다.fill 속성을 설정하고 부모 요소에 position: relative 를 설정해야 합니다.object-fit 스타일을 선택적으로 설정할 수 있습니다.Coworkers 프로젝트에서는 반응형 이미지에 대해 이미 위와 같은 처리를 해 주었기 때문에 추가적인 수정은 하지 않았습니다.
CLS (누적 레이아웃 변경) 는 사용자가 예상치 못한 레이아웃 이동을 경험하는 빈도를 수량화하여 시각적인 안정성을 나타내는 지표입니다.
이미지에 width 와 heigth 를 명시하지 않으면 reflow 현상이 발생하여 CLS 가 발생합니다.
Next.js 의
next/image를 사용하면 필수적으로 width 와 heigth 속성을 입력해야 하고, 이 속성을 결합하여 이미지가 로드되기 전 브라우저가 이미지 공간을 예약하도록 비율을 결정하는 역할을 합니다. (너비와 높이를 알 수 없는 경우 fill 속성 사용)
Coworkers 프로젝트의 팀페이지도 Lighthouse 를 통해 검사한 결과 CLS 값이 0.02 로 양호한 수치를 나타내고 있음을 확인했습니다.


Lazy loading 은 사용자가 웹페이지를 로드할 때, 보이지 않는 이미지들을 불필요하게 미리 로드하여 네트워크 대역폭을 낭비하지 않기 위해 사용하는 기술입니다.
예를 들어, 게시판 페이지 첫 화면에서 사용자는 3 개의 게시물만 확인할 수 있지만 50 개의 모든 게시물에 대한 이미지가 로드되면 대역폭을 비효율적으로 사용하게 됩니다.
lazy loading 을 적용하는 방법은 < img >, next/image 의 loading 속성을 이용하는 방법이 있습니다.
위 방법은 브라우저에서 lazy loading 을 지원하는 방법으로, 외부 라이브러리가 필요하지 않습니다.
❗페이지가 로드될 때 표시 영역에 있을 가능성이 있는 이미지, 특히 LCP 이미지는 lazy loading 을 적용하지 않아야 합니다.
loading 속성 사용
Next.js 의 next/image 컴포넌트의 loading 속성을 사용할 수 있습니다. (HTML < img > 태그의 loading 속성과 같습니다.)
loading 속성은 이미지의 로딩 동작을 나타내며, 기본값은 lazy 입니다. *next/image 가 아닌 < img > 에서는 eager 값이 기본값입니다.*
lazy 인 경우, 이미지는 뷰포트에서 계산된 거리에 도달할 때까지 로딩이 연기됩니다.eager 인 경우, 이미지는 즉시 로드됩니다.Coworkers 에서는 next/image 컴포넌트를 사용하고 있기 때문에 기본값으로 lazy loading 이 적용되어 있으며, 이미지가 포함되는 목록의 경우 무한 로딩이나 페이지네이션이 적용되어 있기 때문에 추가적으로 수정해야 할 사항은 없습니다.
이미지 pre-loading 은 사용자가 이미지를 클릭하기 전에 미리 로드하여 로딩 속도를 개선하는 기법입니다.
고해상도의 이미지를 로드하면 발생할 수 있는 버벅임을 해결하기 위해 특정 이벤트가 발생할 때 이미지를 미리 로드하는 방법을 적용할 수 있습니다.
적용 예시 (이벤트 기반 pre-loading)
import { useEffect } from 'react';
function preloadImage(url) {
const img = new Image();
img.src = url;
}
export default function PreloadExample() {
const handleMouseEnter = () => {
preloadImage('/images/high-res-image.webp');
};
return (
<button onMouseEnter={handleMouseEnter}>
고해상도 이미지 보기
</button>
);
}
적용 예시 (Next.js 의 priority 속성 활용)
import Image from 'next/image';
export default function TeamProfile({ group }) {
return (
<Image
src={group.image}
alt="team-profile"
width={100}
height={100}
priority
/>
);
}
Next.js 에서 Image 컴포넌트 속성의 priority 를 true 로 설정하면, preload 됩니다. 그리고 priority 를 사용하는 이미지는 자동으로 지연 로딩이 비활성화됩니다. 또한, LCP 요소로 감지된 이미지에 priority 를 사용하면, 유의미한 개선을 할 수 있다고 설명하고 있습니다. (https://nextjs.org/docs/app/building-your-application/optimizing/images)
<Image
src="/images/Thumbnail_team.webp"
alt="thumbnail"
width={181}
height={64}
priority
/>
LCP 로 감지된 이미지에 위와 같이 priority 속성을 추가한 결과, 아래 lighthouse 분석 결과에서 볼 수 있듯이 priority 가 적용된 경우 약 30ms 빠르게 로드되었음을 확인했습니다.


이렇게 Next.js 에서는 next/image 컴포넌트의 priority 속성을 지정하면 Next.js 가 자동으로 HTML 을 렌더링할 때, HTML 의 태그 내에 다음과 같이 프리로드 태그를 추가합니다. 이렇게 하면 브라우저가 HTML 파싱 시점부터 즉시 이미지를 로드하기 시작해서 사용자에게 더 빠르게 보여줄 수 있습니다.

이미지 최적화는 웹 페이지의 성능 개선에 핵심적인 역할을 합니다. 웹 페이지에서 이미지가 차지하는 비중이 매우 크기 때문에, 이미지 용량을 줄이면 로딩 속도 개선과 사용자 경험 향상에 크게 기여할 수 있습니다.
이와 같이, 다양한 이미지 최적화 전략을 활용하면 웹사이트의 로딩 시간을 단축하고, 사용자에게 빠르고 쾌적한 사용 경험을 제공할 수 있습니다.
https://web.dev/articles/browser-level-image-lazy-loading?hl=ko
https://web.dev/learn/images/webp?hl=ko
https://web.dev/learn/design/responsive-images?hl=ko#sizes
https://developer.mozilla.org/ko/docs/Web/HTML/Element/img
https://nextjs.org/docs/pages/building-your-application/optimizing/images