asset 최적화
이미지·CSS·JavaScript·웹 폰트의 크기를 줄이고 로딩 시점을 조정하며 HTTP 캐시로 반복 전송 비용을 낮춥니다.
이번 장에서는 웹 성능 최적화의 핵심인 에셋 최적화를 다룹니다.
에셋 유형별 파일 크기 축소와 로딩 방식 개선 전략을 정리하고, Core Web Vitals인 LCP(Largest Contentful Paint)와 실험실 지표인 TBT(Total Blocking Time)와의 연관성을 함께 살펴봅니다.
ASSET DELIVERY
자산을 줄이고 초기 화면의 우선순위를 정합니다
이미지·CSS·JavaScript·폰트의 전송 크기와 실행 비용은 다릅니다. 빌드 결과의 URL·캐시 정책과 브라우저의 발견·실행 시점을 함께 설계합니다.
원본·변형 · 필요한 품질
실제 표시 크기에 맞는 이미지 후보를 srcset·sizes로 제공합니다. 폰트는 필요한 문자·굵기를 정하고 품질을 확인합니다.
코드·해시 · 배포 식별
미사용 CSS·JS를 제거하고 필요한 단위로 분할합니다. 내용이 바뀐 파일은 새 해시 URL을 만들고 HTML·manifest의 참조도 갱신합니다.
초기 화면 · 빨리 발견해야 할 자원
LCP 이미지와 핵심 CSS를 먼저 찾도록 합니다. LCP 이미지는 lazy로 미루지 않고 preload는 실제 발견 지연을 확인한 중요한 자원에 제한합니다.
나중 화면 · 사용 시점까지 미룰 자원
화면 밖 이미지에 lazy loading을, 비필수 코드에는 필요한 시점의 동적 import를 검토합니다. async·defer는 그 자체로 “사용할 때까지 다운로드하지 않음”을 뜻하지 않습니다.
일반 외부 script · 실행 순서
async는 준비되면 실행하고 순서를 보장하지 않습니다. defer는 파싱 뒤 문서 순서대로 실행합니다. 둘 다 다운로드와 실행 비용을 없애지는 않습니다.
캐시 · 해시 자산과 HTML
변경되지 않는 해시 자산은 긴 max-age·immutable로 재사용할 수 있습니다. HTML은 정책에 맞게 짧은 캐시 또는 재검증으로 새 자산 참조를 전달합니다.
폰트 교체의 레이아웃 이동, 분할된 JS의 실행 비용, 첫 화면 발견 지연을 다시 측정합니다. 파일 크기만 줄었다고 LCP·INP·CLS가 모두 개선되는 것은 아닙니다.
이미지 최적화
웹 페이지에서 가장 큰 용량을 차지하는 경우가 많은 것이 바로 이미지입니다.
이미지 최적화는 웹 성능 개선에 가장 큰 효과를 줄 수 있는 영역 중 하나입니다.
-
적절한 이미지 포맷 선택
- JPEG: 사진, 복잡한 그래픽 등 풍부한 색상 정보와 디테일이 중요한 이미지에 적합합니다. 손실 압축 방식.
- PNG: 투명한 배경이 필요하거나 로고, 아이콘 등 선명한 경계가 중요한 이미지에 적합합니다. 무손실 압축 방식.
- WebP: Google에서 개발한 최신 이미지 포맷으로, JPEG나 PNG보다 더 나은 압축률을 제공하면서도 품질 손실이 적습니다. 투명도도 지원합니다. 모든 최신 브라우저에서 지원되므로 적극적으로 고려해야 합니다.
- SVG (Scalable Vector Graphics): 로고, 아이콘, 간단한 일러스트 등 벡터 기반 이미지에 적합합니다. 확대/축소에 적합하고 CSS로 스타일링할 수 있습니다. 파일 크기는 기하의 복잡도와 포함 데이터에 따라 커질 수도 있어 실제 결과를 비교합니다.
-
이미지 압축 (Compression)
- 손실 압축(Lossy Compression): JPEG에 적용되며, 일부 데이터 손실을 감수하고 파일 크기를 크게 줄입니다.
- 무손실 압축(Lossless Compression): PNG, GIF, SVG 등에 적용되며, 데이터 손실 없이 파일 크기를 줄입니다.
- 온라인 도구(TinyPNG, Squoosh)나 빌드 도구 플러그인(Webpack, Gulp 등)을 사용하여 이미지 압축을 자동화합니다.
-
이미지 크기 조정 (Resizing)
- 실제로 화면에 표시될 크기에 맞춰 이미지를 최적화합니다. 불필요하게 큰 해상도의 이미지를 사용하지 않습니다.
- 반응형 이미지 (
srcset,sizes속성): 다양한 화면 크기와 해상도에 맞춰 브라우저가 최적의 이미지를 선택하도록 합니다.<img srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1000px) 800px, 1200px" src="medium.jpg" alt="Description">
-
지연 로딩 (Lazy Loading)
- 화면 밖 이미지의 로드를 늦춥니다. native lazy 로딩의 실제 시작 시점은 브라우저가 정하며, 화면에 들어오기 전에 요청을 시작할 수도 있습니다.
loading="lazy"속성을 사용하거나, Intersection Observer API를 사용하여 구현합니다.<!-- 초기 LCP 이미지가 아닌, 화면 밖의 갤러리 이미지 예 --> <img src="gallery.jpg" alt="갤러리 풍경" width="800" height="600" loading="lazy">
-
CDN (Content Delivery Network) 활용
- 전 세계 여러 서버에 이미지를 분산 저장하여 사용자에게 가장 가까운 서버에서 이미지를 제공함으로써 로딩 속도를 단축합니다.
CSS 최적화
CSS 파일은 HTML 렌더링을 차단하므로, 효율적인 CSS 관리가 중요합니다.
-
CSS Minification
- 공백, 주석, 마지막 세미콜론 등을 제거하여 CSS 파일 크기를 줄입니다. (PostCSS, cssnano, Webpack css-minimizer-webpack-plugin 등)
-
사용하지 않는 CSS 제거 (PurgeCSS, UnusedCSS)
- 프로젝트에서 실제로 사용되지 않는 CSS 규칙을 제거합니다. 특히 UI 프레임워크(Bootstrap, Material-UI 등)를 사용할 때 불필요한 CSS가 많을 수 있습니다.
-
Critical CSS (핵심 CSS) 인라인화
- 초기 화면 렌더링에 필요한 최소한의 CSS(Above-the-fold CSS)를
<style>태그를 사용하여 HTML 문서<head>내에 직접 삽입합니다. 이렇게 하면 외부 CSS 파일이 로드될 때까지 렌더링이 지연되는 것을 방지하여 FCP 및 LCP를 개선합니다. - 나머지 CSS는 비동기로 로드합니다.
- 초기 화면 렌더링에 필요한 최소한의 CSS(Above-the-fold CSS)를
-
CSS 스프라이트 (CSS Sprites)
- 여러 개의 작은 아이콘 이미지를 하나의 큰 이미지로 합치고,
background-position을 사용하여 필요한 부분만 보이게 합니다. HTTP 요청 수를 줄여 성능을 향상시킵니다. (최근에는 SVG 아이콘 폰트, 인라인 SVG 등으로 대체되기도 함)
- 여러 개의 작은 아이콘 이미지를 하나의 큰 이미지로 합치고,
-
미디어 쿼리 최적화
@import대신<link>태그의media속성을 사용할 때 특정 미디어(예:print)의 적용 조건과 렌더링 차단을 구별합니다. 적용되지 않는 CSS도 미리 다운로드될 수 있으므로 전송 생략을 보장하지 않습니다.
JavaScript 최적화
JavaScript는 파싱, 컴파일, 실행 과정에서 많은 CPU 시간을 소모하고 네트워크 병목을 유발할 수 있습니다.
-
JavaScript Minification & Uglification
- 코드 압축뿐만 아니라 변수명 단축 등 난독화를 통해 파일 크기를 최소화합니다. (Terser, UglifyJS 등)
-
Tree Shaking (트리 쉐이킹)
- Webpack, Rollup과 같은 모듈 번들러를 사용하여 실제로 사용되는 코드만 최종 번들에 포함시키고, 사용되지 않는 코드는 제거합니다. 정적 ES 모듈 구조와 패키지의 부작용 정보 등 번들러가 제거의 안전성을 판단할 조건이 필요합니다. 특히 큰 라이브러리(Lodash 등)의 일부 기능만 사용할 때 결과 번들을 비교합니다. Webpack의 트리 쉐이킹 조건을 참고하되 파일을 분할하는 것과 미사용 코드를 제거하는 것을 구별합니다.
-
코드 스플리팅 (Code Splitting)
- 애플리케이션의 번들을 여러 작은 조각으로 분리하여, 사용자가 현재 필요한 코드만 다운로드하도록 합니다. React의
React.lazy()와Suspense, Vue의 비동기 컴포넌트, Webpack의dynamic import()등을 활용합니다.
- 애플리케이션의 번들을 여러 작은 조각으로 분리하여, 사용자가 현재 필요한 코드만 다운로드하도록 합니다. React의
-
비동기 로딩 (
async/defer속성)- 외부 classic
<script>의async·defer는 다운로드를 HTML 파싱과 병행하게 합니다.async: 준비되는 대로 실행하며 실행 중 파싱을 멈출 수 있습니다. 스크립트 사이 실행 순서가 보장되지 않으므로 의존성을 구분합니다.defer: HTML 파싱이 끝난 후 문서 순서대로 실행합니다. 같은 지연 스크립트 사이의 순서 의존성을 표현할 때 사용합니다.
- 외부 classic
-
서드 파티 스크립트 최적화
- Google Analytics, 광고 스크립트, 소셜 미디어 위젯 등 외부 스크립트는 성능에 큰 영향을 미칠 수 있습니다. 필요한 경우에만 로드하거나, 지연 로딩 또는
async/defer속성을 적용합니다.
- Google Analytics, 광고 스크립트, 소셜 미디어 위젯 등 외부 스크립트는 성능에 큰 영향을 미칠 수 있습니다. 필요한 경우에만 로드하거나, 지연 로딩 또는
-
불필요한 라이브러리 제거
- 작은 기능을 위해 큰 라이브러리 전체를 포함하는 대신, 해당 기능만 직접 구현하거나 더 가벼운 대안을 찾습니다.
-
Long Task 방지
- 메인 스레드를 50ms 넘게 블로킹하는 긴 JavaScript 실행을 피합니다. 복잡한 계산은 Web Workers를 사용하여 백그라운드 스레드에서 처리합니다.
폰트 (Web Font) 최적화
웹 폰트는 디자인 일관성을 제공하지만, 용량이 커서 로딩 성능에 악영향을 미칠 수 있습니다.
-
Web Font 형식 최적화
WOFF2(Web Open Font Format 2.0) 사용: 가장 최신이며 압축률이 뛰어납니다. 대부분의 최신 브라우저에서 지원합니다.WOFF,EOT,TTF등은 구형 브라우저 지원을 위해 폴백(fallback)으로 제공할 수 있습니다.
-
Subset Font (부분 집합 폰트)
- 실제로 웹 페이지에서 사용되는 문자(예: 한글 폰트의 경우 필요한 한글 자모 및 숫자, 영문)만 포함하여 폰트 파일 크기를 줄입니다.
-
font-display속성 사용- CSS
@font-face규칙에서font-display속성을 사용하여 폰트 로딩 방식을 제어합니다.swap: 폰트가 로드되기 전까지는 시스템 폰트를 사용하고, 로드된 후에는 웹 폰트로 교체합니다. FOUT(Flash Of Unstyled Text)가 발생할 수 있지만, 텍스트가 바로 표시되므로 사용자 경험에 좋습니다.optional: 매우 짧은 block 기간 뒤 swap 기간을 두지 않습니다. 제때 준비되지 않은 폰트는 이 표시에서 대체 폰트를 계속 사용하게 될 수 있습니다. 다운로드를 항상 중단한다는 뜻으로 일반화하지 않습니다. font-display의 표시 시간선을 확인합니다.
- CSS
-
preload링크 태그 사용- 가장 중요한 폰트 파일에 대해
<link rel="preload" href="your-font.woff2" as="font" type="font/woff2" crossorigin>태그를 사용하여 브라우저가 가능한 한 빨리 폰트를 가져오도록 지시합니다.
- 가장 중요한 폰트 파일에 대해
HTTP 캐싱 전략 강화
asset 최적화에서는 위험 신호, 최적화 지점, 사용자 영향도를 확인합니다.
HTTP 캐시 저장·재검증 생명주기 — 상태 추적
저장 body·ETag를 이용한 조건부 GET에서 304와 200은 서로 다른 응답 대안입니다. 304는 검증된 기존 body를 사용하고, 200은 새 응답의 저장 정책을 확인합니다.
HTTP CACHE
캐시는 저장 여부와 재사용 조건을 따로 판단합니다
같은 URL을 다시 요청할 때 저장된 응답의 존재·신선도·정책을 확인합니다. 캐시가 오래됐다는 이유만으로 본문을 바로 삭제하지 않습니다.
저장된 응답 없음 · miss
네트워크로 요청합니다. 받은 응답이 저장 가능한 정책이면 본문과 헤더·신선도 정보를 저장합니다.
fresh · 재사용 허용
max-age 등 신선도 기간 안이며 재검증 요구가 없으면 저장된 응답을 사용합니다. 요청·응답의 다른 캐시 조건도 확인합니다.
재검증 필요 · stale / no-cache
stale이거나 no-cache가 재검증을 요구하면 서버에 확인합니다. ETag·Last-Modified가 있으면 조건부 요청을 사용합니다.
조건부 요청 → 서버 응답 → 캐시 처리
If-None-Match: "asset-v1"304 Not Modified · 기존 본문 재사용
검증된 기존 본문을 유지하고 응답에 따라 메타데이터를 갱신합니다. 304는 새 본문을 보내지 않습니다.
200 OK · 응답 교체
새 본문과 헤더를 받고 정책이 허용하면 저장합니다. 검증자가 없으면 일반 요청으로 새 응답을 받아야 할 수 있습니다.
no-store · 저장 금지
새 응답을 캐시에 저장하지 않습니다. 이미 저장된 같은 URL의 응답을 자동으로 삭제한다는 뜻은 아닙니다.
private · 개인 캐시
브라우저 같은 개인 캐시는 저장할 수 있지만 공유 캐시는 저장하지 않습니다. no-store와 서로 다른 정책입니다.
no-cache · 재사용 전 검증
저장은 가능하지만 재사용 전 서버 검증을 요구합니다. fresh여도 검증을 생략하지 않습니다.
새 내용 해시 URL은 이전 URL의 응답을 지우는 대신 다른 캐시 항목을 요청합니다. 이 그림은 일반 HTTP 요청의 기본 흐름이며 Service Worker·history 복원·stale 허용 정책은 별도입니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
- 저장된 ETag가 있고 재검증이 필요한 정상 GET을 가정합니다. 304와 200은 같은 요청의 연속 응답이 아닌 독립 대안입니다. 200 장면의 캐시 교체는 새 응답 저장을 정책이 허용한 경우만 추적합니다. no-store는 새 응답 저장 금지이며 이전 동일 URL 응답의 자동 삭제를 뜻하지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 관계·조건 설명 | 노드 5개·관계 4개의 기본 ETag 조건부 GET이다. 저장 응답이 있고 재검증이 필요하며 서버가 성공적으로 판단하는 조건을 가정한다. 동일한 내용의 304는 본문 없이 기존 본문/메타데이터 사용, 변경된 내용의 200은 새 본문과 정책 확인이다. miss/fresh/no-store/private와 다른 오류·ServiceWorker·stale 예외는 아래 보조 설명/별도 범위이고 이 그림에 억지로 추가하지 않는다. mobile도 같다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 저장한 응답 | 캐시: 저장 body+ETag / 요청: If-None-Match | 캐시에 body와 ETag가 있지만 재검증이 필요하다 | stale 응답은 재사용 가능성 확인이 필요하다 |
| 대안 A · 내용 동일 304 | HTTP: 304 / 캐시 body: 재사용 | 서버가 같은 표현임을 확인한다 | 검증자가 같은 표현을 확인했으므로 새 body 없이 기존 body를 재사용한다 |
| 대안 B · 내용 변경 200 | HTTP: 200 새 body / 캐시: 저장 허용 시 현재 표현으로 갱신 | 새 body와 헤더를 받고 저장 허용 정책을 확인한 뒤 캐시를 교체한다 | 내용이 변경되어 새 body를 받으며, 이 사례는 저장이 허용되어 기존 캐시 항목을 교체한다 |
HTTP 캐시 저장·재검증 생명주기 — 상태 추적
저장 body·ETag를 이용한 조건부 GET에서 304와 200은 서로 다른 응답 대안입니다. 304는 검증된 기존 body를 사용하고, 200은 새 응답의 저장 정책을 확인합니다.
HTTP CACHE
캐시는 저장 여부와 재사용 조건을 따로 판단합니다
같은 URL을 다시 요청할 때 저장된 응답의 존재·신선도·정책을 확인합니다. 캐시가 오래됐다는 이유만으로 본문을 바로 삭제하지 않습니다.
저장된 응답 없음 · miss
네트워크로 요청합니다. 받은 응답이 저장 가능한 정책이면 본문과 헤더·신선도 정보를 저장합니다.
fresh · 재사용 허용
max-age 등 신선도 기간 안이며 재검증 요구가 없으면 저장된 응답을 사용합니다. 요청·응답의 다른 캐시 조건도 확인합니다.
재검증 필요 · stale / no-cache
stale이거나 no-cache가 재검증을 요구하면 서버에 확인합니다. ETag·Last-Modified가 있으면 조건부 요청을 사용합니다.
조건부 요청 → 서버 응답 → 캐시 처리
If-None-Match: "asset-v1"304 Not Modified · 기존 본문 재사용
검증된 기존 본문을 유지하고 응답에 따라 메타데이터를 갱신합니다. 304는 새 본문을 보내지 않습니다.
200 OK · 응답 교체
새 본문과 헤더를 받고 정책이 허용하면 저장합니다. 검증자가 없으면 일반 요청으로 새 응답을 받아야 할 수 있습니다.
no-store · 저장 금지
새 응답을 캐시에 저장하지 않습니다. 이미 저장된 같은 URL의 응답을 자동으로 삭제한다는 뜻은 아닙니다.
private · 개인 캐시
브라우저 같은 개인 캐시는 저장할 수 있지만 공유 캐시는 저장하지 않습니다. no-store와 서로 다른 정책입니다.
no-cache · 재사용 전 검증
저장은 가능하지만 재사용 전 서버 검증을 요구합니다. fresh여도 검증을 생략하지 않습니다.
새 내용 해시 URL은 이전 URL의 응답을 지우는 대신 다른 캐시 항목을 요청합니다. 이 그림은 일반 HTTP 요청의 기본 흐름이며 Service Worker·history 복원·stale 허용 정책은 별도입니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
- 저장된 ETag가 있고 재검증이 필요한 정상 GET을 가정합니다. 304와 200은 같은 요청의 연속 응답이 아닌 독립 대안입니다. 200 장면의 캐시 교체는 새 응답 저장을 정책이 허용한 경우만 추적합니다. no-store는 새 응답 저장 금지이며 이전 동일 URL 응답의 자동 삭제를 뜻하지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 관계·조건 설명 | 노드 5개·관계 4개의 기본 ETag 조건부 GET이다. 저장 응답이 있고 재검증이 필요하며 서버가 성공적으로 판단하는 조건을 가정한다. 동일한 내용의 304는 본문 없이 기존 본문/메타데이터 사용, 변경된 내용의 200은 새 본문과 정책 확인이다. miss/fresh/no-store/private와 다른 오류·ServiceWorker·stale 예외는 아래 보조 설명/별도 범위이고 이 그림에 억지로 추가하지 않는다. mobile도 같다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 저장한 응답 | 캐시: 저장 body+ETag / 요청: If-None-Match | 캐시에 body와 ETag가 있지만 재검증이 필요하다 | stale 응답은 재사용 가능성 확인이 필요하다 |
| 대안 A · 내용 동일 304 | HTTP: 304 / 캐시 body: 재사용 | 서버가 같은 표현임을 확인한다 | 검증자가 같은 표현을 확인했으므로 새 body 없이 기존 body를 재사용한다 |
| 대안 B · 내용 변경 200 | HTTP: 200 새 body / 캐시: 저장 허용 시 현재 표현으로 갱신 | 새 body와 헤더를 받고 저장 허용 정책을 확인한 뒤 캐시를 교체한다 | 내용이 변경되어 새 body를 받으며, 이 사례는 저장이 허용되어 기존 캐시 항목을 교체한다 |
모든 에셋에 적용될 수 있는 중요한 최적화 기법입니다.
-
Cache-Control 헤더
- 서버 응답 시
Cache-ControlHTTP 헤더를 사용하여 브라우저 캐싱 정책을 명확하게 지시합니다. public, max-age=31536000, immutable: 정적 에셋에 대해 긴 캐싱 기간을 설정하여 재방문 시 즉시 로드되도록 합니다.immutable은 해당 응답의 freshness 수명 동안 재검증을 생략할 수 있음을 뜻합니다. 파일명에 content hash를 넣고 변경 시 새 URL을 배포합니다.no-cache: 서버에 항상 재검증 요청을 보내지만, 유효한 캐시가 있으면 사용합니다.no-store: 캐시를 저장하지 않습니다. (매우 민감한 정보에 사용)
- 서버 응답 시
-
ETag / Last-Modified
- 서버에서 파일이 변경되었는지 확인하는 데 사용되는 유효성 검사 토큰입니다. 브라우저가 캐시된 에셋을 다시 요청할 때
If-None-Match또는If-Modified-Since헤더를 포함하여 서버에 전송하고, 서버는 파일이 변경되지 않았으면304 Not Modified응답을 보냅니다. 이는 파일 자체를 재전송하는 것보다 훨씬 효율적입니다.
- 서버에서 파일이 변경되었는지 확인하는 데 사용되는 유효성 검사 토큰입니다. 브라우저가 캐시된 에셋을 다시 요청할 때
-
버전 관리 (Cache Busting)
- 정적 에셋 파일명에 해시 값이나 버전 번호를 포함시켜 파일 내용이 변경될 때마다 새로운 파일명으로 배포합니다.
- (예:
bundle.1a2b3c4d.js,style.v1.2.3.css) - 새 파일명은 새 캐시 키가 됩니다. 사용자가 새 파일을 받으려면 HTML이나 manifest도 그 새 URL을 참조해야 하며, 오래된 HTML이 구 URL을 계속 가리키는 경우까지 자동으로 해결하지는 않습니다.
에셋 최적화는 모든 파일을 똑같이 줄이는 작업이 아니라, 초기 화면과 지연 로드 자원을 나눠 우선순위를 정하는 작업입니다.
예시
전송량만 계산하는 모델을 정합니다. 첫 화면의 이미지 200kB, 로고 20kB, 폰트 80kB, JS 400kB와 화면 밖 갤러리 300kB를 모두 처음 요청하면 합계 1000kB입니다. 갤러리만 필요한 시점으로 미루면 초기 대상은 200 + 20 + 80 + 400 = 700kB로 30% 줄지만, 이후 갤러리를 보면 다시 300kB를 받습니다. 이 모델은 캐시·압축 차이를 제외하며 전송량 감소가 LCP 30% 단축을 보장하지 않습니다. 초기 LCP 이미지를 함께 lazy로 미루는 것은 반대 결과를 낼 수 있습니다.
HTTP 캐시에서는 이미 저장된 응답의 현재 age가 600초이고 max-age=3600이며 다른 재검증 요구가 없다고 가정하면 freshness가 3000초 남습니다. age가 3700초가 되면 만료되지만 본문이 곧바로 사라진다는 뜻은 아닙니다. 저장된 ETag를 조건부 GET에 보내 304를 받으면 기존 본문을 사용하고 메타데이터를 갱신합니다. 네트워크 왕복과 헤더 전송은 여전히 있습니다.
① 같은 저장 응답이 age 60초이지만 응답 정책에 인자 없는 no-cache가 있다면 서버 확인을 생략할 수 있나요? ② 검증 후 200과 새 본문이 왔을 때 기존 본문으로 계속 표시해야 하나요? ③ 새 해시 파일을 배포했지만 사용자의 오래된 HTML이 구 파일명을 참조하면 새 파일이 자동으로 선택되나요? ④ 위 전송량 모델에서 JS를 400→250kB로 줄이고 갤러리를 미루면 초기 합계와 최초 1000kB 대비 감소율은 얼마인가요?
정책과 수량 확인
① fresh여도 재사용 전에 성공적인 검증이 필요합니다. ② 이 성공 응답의 새 본문을 사용하며 저장 가능 여부는 새 정책으로 판단합니다. ③ 구 URL은 별도의 캐시 항목이므로 HTML·manifest 참조도 새 URL로 갱신해야 합니다. ④ 200 + 20 + 80 + 250 = 550kB, 감소분 450kB이므로 45%입니다. 실제 배포의 압축 크기나 실행 시간 측정값으로 확대하지 않습니다. HTTP 캐시의 재사용·검증 조건은 이 기본 GET 모델보다 넓으므로 Service Worker나 stale 허용 정책은 따로 구별합니다.
아키텍처 적용 검증 체크리스트
개념 점검- 이미지/CSS/JS/폰트 각각에서 가장 영향이 큰 최적화 포인트를 설명할 수 있는가?
- 캐시 전략(
max-age,immutable,ETag)의 차이를 구분할 수 있는가?
- 정적 에셋에 해시 기반 파일명 전략(Cache Busting)을 적용했는가?
- 초기 화면에 필요한 리소스와 지연 로드 리소스를 분리했는가?
- 성능 개선 후 수치 검증은 13장 3절의 성능 감사 흐름으로 재확인했는가?
배포 체크 요약
이번 장에서는 웹 성능 최적화의 핵심적인 부분인 에셋 최적화에 대해 심층적으로 학습했습니다.
이번 절에서는 이미지, CSS, JavaScript, 폰트의 파일 크기를 줄이고 로딩 방식을 개선하는 전략을 정리했습니다.
포맷 선택, 압축, 반응형 이미지, 지연 로딩, 미니피케이션, 트리 쉐이킹, 코드 스플리팅, 비동기 로딩, 웹 폰트 최적화, HTTP 캐싱을 에셋별로 검토합니다.
에셋 최적화는 단순히 파일 크기를 줄이는 것을 넘어, 브라우저의 렌더링 과정을 이해하고 사용자에게 필요한 리소스만을 필요한 시점에 제공하는 복합적인 노력입니다.
필요한 리소스를 적절한 시점에 제공하면 페이지 로딩과 상호작용 성능을 개선할 수 있습니다.
에셋 최적화의 마지막 판단은 초기 화면, 지연 로드, 캐시 정책을 하나의 배포 루틴으로 묶어 확인하는 것입니다.