지난 글에서는 Lighthouse 리포트를 기반으로 이미지 최적화가 왜 중요한지, 그리고 이를 위한 다양한 대응 방안들을 소개했다.
이번 글에서는 제가 직접 React + HTML 프로젝트에 최적화 기법을 적용한 과정을 구체적으로 공유해보려 한다.
모든 실험은 Chrome 시크릿 모드에서 진행했으며 최종 성능 개선 결과는 글 마지막에 첨부할 예정이다.
우선 적용한 최적화 방법 정리
이번 성능 개선 작업에서는 LCP(Largest Contentful Paint)를 최적화하기 위해 총 4가지 기법을 실제로 적용했다. 각 기법은 이미지 렌더링과 관련된 브라우저 동작에 직접적으로 영향을 주는 요소들로, 아래와 같은 이유로 선택되었다.
1. WebP 포맷 적용 – 더 가볍고 빠르게
우선 기존의 PNG 이미지 대신 WebP 포맷으로 변경했다. WebP는 구글에서 개발한 차세대 이미지 포맷으로, 같은 품질이라도 용량이 훨씬 작기 때문에 페이지 로딩 속도에 유리하다. 특히 LCP 요소가 이미지인 경우, 그 용량 차이는 사용자 체감 성능에 큰 영향을 줄 수 있어 필수적인 최적화 중 하나다.
2. Preload 설정 – 브라우저에게 미리 알려주기
<head> 태그에 <link rel="preload">를 이용해 주요 이미지를 사전에 로드하도록 설정했다. 이 방법은 브라우저가 HTML 구조를 분석하면서 중요 리소스를 우선적으로 요청하게끔 도와주는 역할을 한다. 결과적으로, 이미지가 실제로 렌더링되는 시점까지의 시간(LCP)을 줄이는 데 효과적이었다.
3. Eager 로딩 – 바로 불러오기
기본적으로 이미지에는 loading="lazy" 속성이 사용되곤 하지만, LCP와 직결되는 주요 이미지에는 오히려 eager 로딩이 적절하다. loading="eager" 속성을 사용하면 브라우저는 해당 이미지를 가능한 빨리 로드하도록 처리한다. 특히 메인 콘텐츠로서 가장 큰 이미지가 곧바로 보이는 것이 중요한 경우, 이 설정은 실질적인 개선을 이끌 수 있다.
4. 동기 디코딩 – 렌더링과 함께 디코딩
마지막으로 <img> 태그에 decoding="sync" 속성을 추가해, 이미지를 비동기가 아닌 동기 방식으로 디코딩하도록 설정했다. 이 속성은 브라우저가 이미지 디코딩을 별도 스레드에서 처리하지 않고, 렌더링 타이밍과 맞춰 처리하게 한다. 그 결과, 이미지가 화면에 더 빠르게 렌더링되도록 도와준다.
적용 코드 상세
preload를 활용한 사전 이미지 로드
<link rel="preload" as="image" href="/codingzone_main_v5.webp" />
<link rel="preload" as="image" href="/coding-zone-main2.webp" />
<link rel="preload" as="image" href="/coding-zone-main3.webp" />
<link rel="preload" as="image" href="/Codingzone-noregist.webp" />
- 주요 이미지에 대해 preload를 설정함으로써 브라우저가 렌더링 전에 리소스를 미리 요청하도록 함.
- 특히 LCP 요소로 지적된 Codingzone-noregist.webp 이미지의 로딩 타이밍을 앞당기기 위해 설정.
React 컴포넌트 내 적용 코드
<div className="codingzone-list">
<picture className={`no-classes-image ${showNoClassesImage ? 'visible' : 'hidden'}`}>
<source srcSet="/Codingzone-noregist.webp" type="image/webp" />
<img
src="/Codingzone-noregist.png"
alt="수업이 없습니다"
className="no-classes-img"
width="600"
height="260"
loading="eager"
decoding="sync"
/>
</picture>
{!showNoClassesImage && (
<ClassList
classList={classList}
handleCardClick={handleCardClick}
handleToggleReservation={handleToggleReservation}
isAdmin={isAdmin}
onDeleteClick={handleDelete}
userReservedClass={userReservedClass}
token={token}
/>
)}
</div>
핵심 포인트
- loading="eager": 렌더링 시점에서 즉시 이미지 로딩 유도
- decoding="sync": 브라우저가 디코딩을 즉시 수행하도록 요청
- .webp 우선 렌더링, .png는 fallback
왜 lazy loading을 피했나?
- Lazy loading은 비가시 영역 최적화엔 좋지만, 초기 렌더링 요소인 LCP에 포함된 이미지는 오히려 느려질 수 있음
- Lighthouse 기준 LCP 요소는 최대한 빨리 로딩돼야 하므로 eager로 강제 설정
HTML 파일 수정 사항
아래는 preload를 적용한 실제 HTML <head> 부분 코드입니다:
<link rel="preload" as="image" href="/codingzone_main_v5.webp" />
<link rel="preload" as="image" href="/coding-zone-main2.webp" />
<link rel="preload" as="image" href="/coding-zone-main3.webp" />
<link rel="preload" as="image" href="/Codingzone-noregist.webp" />
용어 정리
LCP (Largest Contentful Paint) | 사용자에게 화면이 보이기 시작한 후, 가장 큰 시각 요소(주로 이미지나 큰 텍스트 블록)가 렌더링 완료되기까지 걸리는 시간 |
WebP | Google이 개발한 이미지 포맷, 기존 JPEG/PNG보다 훨씬 가볍고 효율적 |
Preload | 렌더링에 중요한 리소스를 사전 로드하여 지연 방지 |
eager loading | 브라우저가 이미지를 즉시 로드하도록 지시 |
decoding="sync" | 디코딩을 브라우저 렌더링과 동시에 진행시켜 지연을 최소화 |
적용 전후 성능 비교는?
성능 개선 결과
Performance | 82 | 94 |
Largest Contentful Paint | 1.9s | 1.6s |
Cumulative Layout Shift | 0.202 | 0.005 |
퍼포먼스 점수가 12점 상승했고,
LCP(가장 큰 콘텐츠 요소 표시 시간)가 0.3초 줄었으며,
CLS(레이아웃 이동) 문제도 거의 사라졌습니다.
'[refactor: advICE]' 카테고리의 다른 글
[refactor] Lv.4 LCP 성능 향상을 위한 이미지 최적화 전략 정리 (0) | 2025.04.07 |
---|---|
[refactor] Lv.3 React 프로젝트에서 명명 규칙 통일하기 (feat. PascalCase) (0) | 2025.03.28 |
[refactor] Lv.2.2 RTR 도입기: 진짜 로그아웃은 Refresh Token까지 지우는 것부터 (1) | 2025.03.27 |
[refactor] Lv.2.1 RTR 도입기: 액세스 토큰 만료? 자동 재발급으로 해결하기 (0) | 2025.03.25 |
[refactor] Lv.2 RTR 도입기: 쿠키 기반 로그인 응답 처리 (0) | 2025.03.24 |