asset 최적화
이번 장에서는 웹 성능 최적화의 핵심적인 부분인 에셋 최적화에 대해 심층적으로 다룰 것입니다. 각 에셋 유형별로 어떻게 파일 크기를 줄이고 로딩 방식을 효율적으로 가져갈 수 있는지 구체적인 전략과 기술들을 알아보겠습니다. 에셋 최적화는 LCP(Largest Contentful Paint)와 TBT(Total Blocking Time)와 같은 Core Web Vitals 지표 개선에 직접적인 영향을 미치며, 사용자에게 빠르고 쾌적한 웹 경험을 제공하는 데 필수적입니다.
이미지 최적화
웹 페이지에서 가장 큰 용량을 차지하는 경우가 많은 것이 바로 이미지입니다. 이미지 최적화는 웹 성능 개선에 가장 큰 효과를 줄 수 있는 영역 중 하나입니다.
- 적절한 이미지 포맷 선택
- 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)
- 뷰포트(viewport)에 들어올 때까지 이미지를 로드하지 않도록 지연시킵니다.
loading="lazy"
속성을 사용하거나, Intersection Observer API를 사용하여 구현합니다.<img src="placeholder.jpg" data-src="actual-image.jpg" loading="lazy" alt="Lazy Loaded Image">
- 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과 같은 모듈 번들러를 사용하여 실제로 사용되는 코드만 최종 번들에 포함시키고, 사용되지 않는 코드는 제거합니다. 이는 특히 큰 라이브러리(Lodash 등)의 일부 기능만 사용할 때 유용합니다.
- 코드 스플리팅 (Code Splitting)
- 애플리케이션의 번들을 여러 작은 조각으로 분리하여, 사용자가 현재 필요한 코드만 다운로드하도록 합니다. React의
React.lazy()
와Suspense
, Vue의 비동기 컴포넌트, Webpack의dynamic import()
등을 활용합니다.
- 애플리케이션의 번들을 여러 작은 조각으로 분리하여, 사용자가 현재 필요한 코드만 다운로드하도록 합니다. React의
- 비동기 로딩 (
async
/defer
속성)<script>
태그에async
또는defer
속성을 추가하여 HTML 파싱을 차단하지 않도록 합니다.async
: 스크립트를 비동기적으로 로드하고, 로드가 완료되는 대로 바로 실행합니다. 스크립트 간 의존성이 없는 경우에 사용합니다.defer
: 스크립트를 비동기적으로 로드하지만, HTML 파싱이 완료된 후에 문서 순서대로 실행합니다. 스크립트 간 순서 의존성이 있는 경우에 사용합니다.
- 서드 파티 스크립트 최적화
- 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
: 네트워크 상황이 좋지 않으면 웹 폰트 로드를 포기하고 시스템 폰트를 사용합니다.
- CSS
preload
링크 태그 사용- 가장 중요한 폰트 파일에 대해
<link rel="preload" href="your-font.woff2" as="font" type="font/woff2" crossorigin>
태그를 사용하여 브라우저가 가능한 한 빨리 폰트를 가져오도록 지시합니다.
- 가장 중요한 폰트 파일에 대해
HTTP 캐싱 전략 강화
모든 에셋에 적용될 수 있는 중요한 최적화 기법입니다.
- Cache-Control 헤더
- 서버 응답 시
Cache-Control
HTTP 헤더를 사용하여 브라우저 캐싱 정책을 명확하게 지시합니다. public, max-age=31536000, immutable
: 정적 에셋에 대해 긴 캐싱 기간을 설정하여 재방문 시 즉시 로드되도록 합니다.immutable
은 파일이 절대 변경되지 않음을 브라우저에 알려줍니다.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
) - 이를 통해 캐시된 구 버전 파일이 아닌 최신 버전의 파일을 사용자가 항상 받도록 강제할 수 있습니다.
마무리하며
이번 장에서는 웹 성능 최적화의 핵심적인 부분인 에셋 최적화에 대해 심층적으로 학습했습니다.
여러분은 웹 페이지를 구성하는 주요 에셋들인 이미지, CSS, JavaScript, 폰트 각각에 대해 파일 크기를 줄이고 로딩 방식을 효율적으로 가져가는 다양한 전략과 기술들을 구체적으로 살펴보았습니다. 이미지 포맷 선택, 압축, 반응형 이미지, 지연 로딩부터 CSS 미니피케이션, 사용하지 않는 CSS 제거, Critical CSS 인라인화, 그리고 JavaScript 미니피케이션, 트리 쉐이킹, 코드 스플리팅, 비동기 로딩, 웹 폰트 형식 최적화 등 실질적인 최적화 방법들을 익혔습니다. 또한, HTTP 캐싱 전략을 강화하여 에셋의 재로드 비용을 최소화하는 방법을 다시 한번 강조했습니다.
에셋 최적화는 단순히 파일 크기를 줄이는 것을 넘어, 브라우저의 렌더링 과정을 이해하고 사용자에게 필요한 리소스만을 필요한 시점에 제공하는 복합적인 노력입니다. 이 장에서 배운 지식들을 바탕으로 여러분의 웹 애플리케이션이 항상 빠르고 쾌적하게 동작하여 사용자에게 최고의 경험을 제공할 수 있기를 바랍니다.