📖 기술 가이드

11개 실습 각각에 대해 기술적 설명 · 측정 방법 · 측정 지표의 의미 · 실무 베스트 프랙티스를 정리했습니다. 각 실습 페이지의 "🔧 실제 구현 코드" 박스가 "무엇을 했는지"를 보여준다면, 이 페이지는 "왜 그게 빨라지는지"와 "실무에서는 어떻게 하는지"를 설명합니다.

목차

  1. 공통 측정 지표 해설
  2. 1. 스크립트 병합
  3. 2. 스크립트 최소화
  4. 3. 스크립트 압축 전달 (gzip/br)
  5. 4. 이미지 포맷 최적화
  6. 5. 이미지 (비)손실 압축
  7. 6. 브라우저 캐시 (Cache-Control)
  8. 7. DNS Prefetch
  9. 8. CSS/JS 위치 조절
  10. 9. 페이지 Prefetch
  11. 10. 3rd-party 스크립트 조정
  12. 11. HTTP/1.1 vs HTTP/2 vs HTTP/3

📊 공통 측정 지표 해설

모든 실습은 /js/perf-dashboard.js가 각 방문자의 브라우저 안에서 브라우저 내장 Performance API (Navigation Timing, Resource Timing, PerformanceObserver)만으로 측정합니다. 서버나 별도 도구는 전혀 개입하지 않습니다.

지표측정 방법의미
페이지 로드 시간navigation.loadEventEnd - startTime문서와 모든 하위 리소스(이미지, 스크립트 등)가 전부 로드 완료된 시점.
DOMContentLoadednavigation.domContentLoadedEventEndHTML 파싱과 동기 스크립트 실행이 끝난 시점. 이미지 등 비동기 리소스는 기다리지 않아 체감 속도에 더 가까움.
First Contentful Paint (FCP)PerformanceObserver({type:"paint"})화면에 처음으로 무언가(텍스트/이미지)가 그려진 시점.
Largest Contentful Paint (LCP)PerformanceObserver({type:"largest-contentful-paint"})가장 큰 콘텐츠 요소가 그려진 시점. Core Web Vitals의 핵심 지표.
요청 수performance.getEntriesByType("resource").length문서 포함 총 HTTP 요청 수. 요청이 많을수록 네트워크 왕복(RTT) 오버헤드가 누적됨.
전송 크기 (transferSize)resource.transferSize네트워크로 실제 전송된 바이트(HTTP 헤더 + 압축된 바디). 캐시 히트 시 0에 가까움.
압축 해제 크기 (decodedBodySize)resource.decodedBodySize압축을 푼 뒤 실제 콘텐츠 바이트 수. transferSize와 비교하면 압축/캐시 효과가 드러남.

1. 스크립트 병합 (Script Combination)

기술적 설명

HTTP/1.1에서 브라우저는 보통 origin당 동시 연결을 6개로 제한합니다. 작은 JS 파일을 여러 개로 쪼개면 요청마다 헤더 오버헤드와 큐잉 지연이 더해지고, 7번째 요청부터는 앞 요청이 끝나길 기다려야 합니다. 빌드 시점에 여러 파일을 하나로 이어붙이면(concat) 요청 수 자체가 줄어듭니다.

측정 방법

before.html(20개 <script src>)과 after.html(병합된 bundle.js 1개)을 각각 로드해 요청 수와 로드 시간을 비교합니다.

측정 지표 해석

요청 수(20 → 1)가 가장 직접적인 증거입니다. 로드 시간 단축 폭은 네트워크 지연(RTT)이 클수록 커지므로, 로컬호스트에서는 효과가 작아 보일 수 있습니다 — DevTools Network 쓰로틀링(Fast 3G 등)으로 확인해 보세요.

베스트 프랙티스

2. 스크립트 최소화 (Minification)

기술적 설명

주석, 공백, 긴 변수명은 실행에는 필요 없지만 전송 바이트 수를 늘립니다. Terser 같은 도구가 코드의 동작을 바꾸지 않으면서 이런 요소를 제거/축약합니다.

측정 방법

같은 로직을 담은 비최소화 파일과 terser로 최소화한 파일을 각각 로드합니다.

측정 지표 해석

encodedBodySize/decodedBodySize 감소가 핵심이며, 파싱·컴파일 비용이 줄어 DOMContentLoaded도 약간 당겨집니다(저사양 기기일수록 체감이 큼).

베스트 프랙티스

3. 스크립트 압축 전달 (gzip / br)

기술적 설명

Express의 compression 미들웨어는 요청의 Accept-Encoding 헤더를 보고 gzip 또는 br(Brotli)로 압축한 뒤 Content-Encoding 헤더와 함께 응답합니다. 텍스트 기반 자산(JS/CSS/HTML/JSON)은 보통 60~80%까지 압축됩니다.

측정 방법

동일한 대용량 JS 파일을 before(압축 없음)와 after(gzip/br 압축)로 각각 서빙합니다.

측정 지표 해석

전송 크기(transferSize)와 압축 해제 크기(decodedBodySize)를 나란히 비교하는 것이 핵심입니다. before는 둘이 거의 같고, after는 전송 크기만 크게 줄어듭니다 — 브라우저가 실행하는 코드는 동일하지만 "네트워크로 나른 바이트"만 줄었다는 뜻입니다.

베스트 프랙티스

4. 브라우저 이미지 포맷 최적화

기술적 설명

WebP/AVIF는 JPEG보다 최신 압축 알고리즘(각각 VP8, AV1 기반 인트라 압축)을 사용해 같은 체감 화질에서 더 작은 용량을 냅니다.

측정 방법

같은 사진을 jpg와 webp(+보너스로 avif)로 서빙하고, 이미지 리소스 1개의 transferSize를 비교합니다. 측정은 지금 열어본 그 브라우저에서 바로 이루어지며, 포맷 지원 여부와 무관하게 실제 전송 바이트를 그대로 기록합니다(상세: 바로 위 안내 문구 참고).

측정 지표 해석

페이지 전체 지표보다 "그 이미지 하나"의 transferSize 감소율을 보는 것이 정확합니다.

베스트 프랙티스

5. 이미지 (비)손실 압축

기술적 설명

무손실(PNG)은 픽셀 정보를 원본 그대로 보존해 용량이 크고, 손실 압축(JPEG quality 조정)은 시각적으로 거의 구분되지 않는 선에서 데이터를 버려 용량을 크게 줄입니다.

측정 방법

같은 사진을 무손실 PNG와 quality 65 JPEG로 서빙해 transferSize를 비교합니다.

측정 지표 해석

보통 10~20배 차이가 나며, 이미지 최적화 중 가장 효과가 큰 단일 조치임을 체감할 수 있습니다.

베스트 프랙티스

6. 브라우저 캐시 (Cache-Control)

기술적 설명

Cache-Control: no-store는 캐시를 전혀 허용하지 않고, public, max-age=31536000, immutable은 1년간 재검증 없이 캐시를 그대로 재사용하라고 브라우저에 지시합니다.

측정 방법

동일 파일을 두 헤더로 각각 서빙하고, 같은 페이지를 새로고침(F5)해서 "최초 방문"과 "재방문"을 비교합니다. navigation.type(navigate/reload)으로 자동 구분해 기록합니다.

측정 지표 해석

after + 재방문 조합에서 transferSize ≈ 0(디스크/메모리 캐시에서 바로 읽음)이지만 decodedBodySize는 그대로입니다 — "캐시 히트는 네트워크를 아예 타지 않는다"는 것을 숫자로 직접 확인하는 것이 핵심 교육 포인트입니다.

베스트 프랙티스

7. DNS Prefetch

기술적 설명

브라우저가 리소스를 실제로 요청하기 전에 미리 DNS 조회를 끝내두면, 요청 시점에 그 지연이 사라집니다. <link rel="dns-prefetch" href="//origin">로 힌트를 줍니다.

측정 방법

nip.io로 만든 별도 origin에서 자산을 로드하며, before=힌트 없음, after=dns-prefetch 힌트 추가. Resource Timing의 domainLookupStart/domainLookupEnd 구간을 비교합니다.

측정 지표 해석

DNS 조회 시간(보통 수십 ms) 자체가 핵심 지표이며, 전체 페이지 로드 시간보다 그 요청 하나의 타이밍 상세를 보는 게 정확합니다. 로컬/저지연 환경에서는 효과가 작게 보일 수 있습니다.

베스트 프랙티스

8. CSS/JS 위치 조절 (Top/Bottom)

기술적 설명

<head>의 동기 <script>는 파서를 멈추고(parser-blocking) 다운로드와 실행이 끝날 때까지 이후 HTML 파싱/렌더링을 차단합니다. </body> 직전으로 옮기거나 defer/async를 붙이면 HTML 파싱과 병렬로 진행됩니다.

측정 방법

메인 스레드를 약 700ms 점유하는 스크립트를 head(before)와 body 하단(after)에 각각 배치합니다.

측정 지표 해석

DOMContentLoaded(및 가능하면 FCP)의 지연 정도가 핵심입니다. 이 실습은 CPU 바운드라 네트워크 쓰로틀링 없이도 효과가 뚜렷합니다.

베스트 프랙티스

9. 페이지 Prefetch

기술적 설명

<link rel="prefetch" href="next.html">는 브라우저에게 "사용자가 곧 이 페이지로 이동할 가능성이 높다"고 알려 유휴 시간에 미리 받아 캐시해두게 합니다.

측정 방법

before=일반 링크, after=prefetch 힌트 추가. 클릭해서 next.html로 이동했을 때의 로드 시간을 ?variant= 파라미터로 구분해 기록합니다.

측정 지표 해석

after variant의 next.html 로드 시간이 거의 0에 수렴합니다 — 이미 받아둔 페이지로 이동하는 것임을 숫자로 확인할 수 있습니다.

베스트 프랙티스

10. 3rd-party 스크립트 조정

기술적 설명

느린 3rd-party 스크립트(분석, 광고, 위젯 등)를 동기 <script>로 문서 중간에 넣으면 그 스크립트의 응답 지연이 그대로 본문 렌더링 지연이 됩니다. async/defer 또는 지연 주입으로 메인 콘텐츠 렌더링과 분리할 수 있습니다.

측정 방법

2초 인위 지연이 걸린 가짜 SDK를 before=동기 blocking, after=비동기/지연 로드로 삽입합니다.

측정 지표 해석

DOMContentLoaded/본문 표시 시점은 after에서 거의 움직이지 않지만, 그 스크립트 자체의 Resource Timing duration은 양쪽 다 2초로 동일합니다 — 3rd-party 자체가 빨라진 게 아니라 메인 콘텐츠 렌더링에서 분리됐을 뿐이라는 중요한 구분입니다.

베스트 프랙티스

11. HTTP/1.1 vs HTTP/2 vs HTTP/3

기술적 설명

HTTP/1.1은 연결당 하나의 요청만 순차 처리하고, 브라우저는 origin당 보통 6개 연결로 병렬성을 흉내냅니다. HTTP/2는 하나의 TCP 연결 위에서 여러 스트림을 멀티플렉싱하고 헤더를 HPACK으로 압축해 이 제약을 없앴지만, TCP 자체의 순서 보장 때문에 패킷 하나가 유실되면 그 연결의 모든 스트림이 멈추는 TCP-레벨 HOLB(Head-of-Line Blocking)가 남아있습니다. HTTP/3는 TCP 대신 UDP 기반 QUIC을 사용해 스트림을 독립적으로 순서 보장하므로, 패킷 유실이 해당 스트림에만 영향을 줍니다. QUIC은 TLS 1.3을 전송 계층에 통합해 연결 수립도 더 빠릅니다(재연결 시 0-RTT).

측정 방법

이미지 30장이 있는 동일 페이지를 http(:3000, HTTP/1.1)와 https(:8443, 자가서명 인증서, HTTP/2)로 각각 서빙합니다. DevTools Network의 Protocol 컬럼(http/1.1 vs h2)과 워터폴 완료 시점을 비교하세요. HTTP/3는 Node.js가 QUIC을 네이티브 지원하지 않아 이 프로젝트에서 실제 서버로 구현하지 않았습니다 — 개념 설명과 함께, 실제 공개 HTTP/3 사이트(예: google.com)를 DevTools로 직접 확인하는 방법을 안내합니다.

측정 지표 해석

요청이 많을수록(동시 연결 제한에 걸릴수록) HTTP/1.1의 워터폴은 계단식으로 밀리고, HTTP/2는 한 번에 쏟아지는 모습이 보입니다. 네트워크 지연을 쓰로틀링으로 키울수록 차이가 더 커집니다.

베스트 프랙티스

← 실습 목록으로 돌아가기