Time to First Byte의 캐시 시간 줄이기
캐시 시간 구간은 서비스 워커와 브라우저 캐시 조회 시간을 측정합니다. 캐싱 전략, Cache-Control 헤더, bfcache, 서비스 워커 최적화로 TTFB를 줄이는 방법을 알아보십시오.
Time to First Byte의 캐시 시간 줄이기
이 글은 Time to First Byte (TTFB) 가이드의 일부입니다. 캐시 시간(cache duration)은 TTFB의 두 번째 하위 요소로, 브라우저가 로컬 캐시와 활성화된 서비스 워커에서 일치하는 응답을 확인하는 데 쓰는 시간입니다. 캐시 시간이 주요 병목인 경우는 드물지만, 서비스 워커를 사용하거나 브라우저 캐싱 전략에 크게 의존하는 사이트라면 반드시 이해해야 합니다.
Time to First Byte (TTFB)는 다음 하위 단계로 나눌 수 있습니다.
- Waiting + Redirect (또는 waiting duration)
- Worker + Cache (또는 cache duration)
- DNS (또는 DNS duration)
- Connection (또는 connection duration)
- Request (또는 request duration)
Time to First Byte 최적화 방법을 찾고 계십니까? 이 글은 Time to First Byte의 캐시 시간 구간을 다룹니다. Time to First Byte를 이해하거나 문제를 해결해야 하는데 캐시 시간의 의미를 잘 모른다면, 이 글을 읽기 전에 Time to First Byte란 무엇인가와 Time to First Byte 문제 진단 및 해결을 먼저 읽으십시오.
참고: 보통 Time to First Byte에서 Cache Duration 구간은 병목이 아닙니다. 따라서 a) 서비스 워커를 사용 중이거나, b) 저처럼 페이지 속도를 깊이 파고드는 분이라면 계속 읽으십시오.
보통 Time to First Byte의 캐시 시간 구간은 병목이 되지 않으며 10~20ms 이내에 완료됩니다. 서비스 워커를 사용할 때는 60ms 미만이면 양호한 수준입니다.
Table of Contents!
서비스 워커가 Time to First Byte에 미치는 영향
서비스 워커는 Time to First Byte (TTFB)에 긍정적인 영향과 부정적인 영향을 모두 줄 수 있습니다. 물론 서비스 워커를 사용하는 웹사이트에만 해당합니다.
서비스 워커가 TTFB에 영향을 주는 방식은 다음과 같습니다.
서비스 워커 시작 시간으로 인한 TTFB 지연: workerStart 값은 요청된 리소스에 서비스 워커가 연결되어 있을 때 해당 서비스 워커가 시작되는 데 걸리는 시간을 나타냅니다. 이 시작 시간은 TTFB 계산에 포함됩니다. 서비스 워커를 새로 시작하거나 종료된 상태에서 다시 깨워야 한다면 초기 응답 시간에 지연이 발생해 TTFB가 증가할 수 있습니다.
캐싱을 통한 TTFB 단축: stale-while-revalidate 같은 캐싱 전략을 사용하면 서비스 워커가 캐시에 있는 콘텐츠를 즉시 제공할 수 있습니다. 브라우저가 서버 응답을 기다릴 필요가 없으므로 자주 접근하는 리소스의 TTFB가 거의 즉각적인 수준으로 단축됩니다. 이 전략은 최신 정보가 필수적인 동적 생성 콘텐츠보다는 업데이트가 드문 콘텐츠에 가장 적합합니다.
앱 셸 모델을 통한 TTFB 단축: 클라이언트 렌더링 애플리케이션의 경우 앱 셸(app shell) 모델을 사용하면 기본 구조의 TTFB를 거의 0에 가깝게 줄일 수 있습니다. 서비스 워커가 캐시에서 기본 페이지 구조를 즉시 전달하고 콘텐츠는 나중에 동적으로 로드하는 방식입니다.
최적화되지 않은 코드로 인한 TTFB 지연: 서비스 워커 코드가 복잡하고 비효율적이면 캐시 조회 과정이 느려져 TTFB도 함께 증가합니다.
서비스 워커는 페이지 속도에 나쁜 영향을 줍니까? 그렇지 않습니다. (대개는) 전혀 나쁘지 않습니다. 시작 시간으로 인해 초기 TTFB가 다소 늘어날 수 있지만, 네트워크 요청 가로채기, 캐시 관리, 오프라인 지원 등의 기능을 활용하면 장기적으로 상당한 성능 향상을 얻을 수 있습니다. 특히 사이트 재방문자에게 큰 효과가 있습니다.
서비스 워커 캐싱 전략
서비스 워커에 적용하는 캐싱 전략에 따라 속도와 최신성 사이의 균형이 결정됩니다. 전략마다 TTFB의 캐시 시간 구간에 미치는 영향이 다릅니다.
Cache-First (캐시 우선, 네트워크 fallback)
서비스 워커가 캐시를 먼저 확인합니다. 캐시된 응답이 있으면 즉시 반환하고, 없으면 네트워크로 요청을 보냅니다. 캐시된 리소스에 대해 가장 빠른 TTFB를 제공하지만 오래된 콘텐츠가 제공될 위험이 있습니다.
적합한 대상: 정적 에셋, 이미지, 폰트, 변경 빈도가 낮은 콘텐츠.
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
return fetch(event.request).then((networkResponse) => {
const cache = await caches.open('v1');
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
})
);
}); Network-First (네트워크 우선, 캐시 fallback)
서비스 워커가 항상 네트워크 요청을 먼저 시도합니다. 네트워크 요청이 실패하면(예: 오프라인 상태) 캐시된 버전을 제공합니다. 네트워크가 연결되어 있을 때 최신 콘텐츠를 보장하지만, 온라인 사용자의 TTFB는 단축되지 않습니다.
적합한 대상: API 응답, 자주 업데이트되는 콘텐츠, 최신성이 중요한 페이지.
Stale-While-Revalidate
서비스 워커가 캐시된 버전을 즉시 반환하여 빠른 TTFB를 확보하는 동시에, 백그라운드에서 네트워크를 통해 업데이트된 버전을 가져옵니다. 새로 가져온 버전은 다음 방문을 위해 캐시를 갱신합니다. 속도와 최신성 사이에서 가장 이상적인 균형을 제공하는 경우가 많습니다.
적합한 대상: 정기적으로 변경되지만 뉴스 기사, 블로그 포스트, 상품 목록처럼 실시간 정확도까지는 필요하지 않은 콘텐츠.
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('v1').then((cache) => {
return cache.match(event.request).then((cachedResponse) => {
const fetchPromise = fetch(event.request).then((networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
return cachedResponse || fetchPromise;
});
})
);
}); Cache-Control 헤더 설정
TTFB의 캐시 시간 구간은 서비스 워커와 브라우저 캐시를 조회하는 시간을 측정하지만, 브라우저가 서버에 접속할 필요가 있는지를 근본적으로 결정하는 것은 올바른 Cache-Control 헤더입니다. 캐시 헤더를 올바르게 설정하면 재방문자의 TTFB 전체를 사실상 우회할 수 있습니다.
리소스 유형별 권장 Cache-Control 설정은 다음과 같습니다.
# HTML 페이지 (항상 재검증) Cache-Control: no-cache # 콘텐츠 해시가 적용된 정적 에셋 (영구 캐시) Cache-Control: public, max-age=31536000, immutable # 콘텐츠 해시가 없는 이미지 (1주일간 캐시) Cache-Control: public, max-age=604800 # API 응답 (캐시 안 함) Cache-Control: no-store
주요 디렉티브 설명:
- no-cache: 브라우저는 캐시된 복사본을 사용하기 전에 반드시 서버에서 재검증해야 합니다. 이는 "캐시하지 말라"는 뜻이 아니라 "항상 먼저 확인하라"는 뜻입니다.
- no-store: 브라우저가 응답을 전혀 캐시하지 않아야 합니다. 민감한 정보나 변화가 매우 빠른 동적 콘텐츠에 사용하십시오.
- max-age: 재검증 없이 캐시에서 응답을 제공할 수 있는 시간(초 단위)입니다.
- immutable: 리소스가 절대 변경되지 않음을 브라우저에 알립니다. 정적 에셋의 경우 콘텐츠 해시가 포함된 파일명(예:
style.a1b2c3.css)과 함께 사용하십시오. - public: 공유 캐시(CDN, 프록시)에 응답을 캐시할 수 있도록 허용합니다. 사용자 맞춤 콘텐츠에는 private을 사용하십시오.
Cloudflare 같은 CDN을 사용하는 경우 엣지 캐싱 규칙도 설정할 수 있습니다. 자세한 방법은 성능을 위한 Cloudflare 설정 가이드를 참고하십시오.
Back/Forward Cache (bfcache)
Back/Forward Cache(bfcache)는 사용자가 다른 페이지로 이동할 때 페이지의 전체 스냅샷을 메모리에 저장하는 브라우저 최적화 기능입니다. 사용자가 뒤로 가기나 앞으로 가기 버튼을 누르면 메모리에서 페이지가 즉시 복원되므로 TTFB를 비롯한 모든 로딩 지표가 완전히 제거됩니다.
네트워크 요청이 전혀 발생하지 않기 때문에 bfcache에서 제공된 페이지는 RUM 데이터에서 TTFB가 0밀리초로 나타납니다. 브라우저가 메모리 스냅샷에서 페이지를 그대로 복원하기 때문입니다.
페이지가 bfcache 대상이 되도록 하려면 다음 사항을 지켜야 합니다.
unload이벤트 리스너를 사용하지 마십시오(대신pagehide사용).- HTML 문서에
Cache-Control: no-store를 사용하지 마십시오. - 페이지가 숨겨질 때 열려 있는 IndexedDB 연결을 닫으십시오.
- 활성 상태의 WebSocket이나 WebRTC 연결을 유지하지 마십시오(
pagehide이벤트에서 연결 종료).
bfcache 적격 여부는 Chrome DevTools의 Application 탭 내 "Back/Forward Cache" 섹션에서 테스트할 수 있습니다. 페이지가 bfcache를 사용하지 못한 구체적인 이유가 Chrome에 표시됩니다.
뒤로 가기/앞으로 가기 이동이 빈번한 사이트(예: 이커머스 카테고리 및 상품 페이지, 검색 결과 페이지)의 경우, bfcache를 활용하면 전체 페이지 탐색의 상당 부분에서 체감 TTFB를 대폭 개선할 수 있습니다.
Time to First Byte의 캐시 시간 측정 방법
Time to First Byte의 캐시 시간은 다음 코드로 측정할 수 있습니다.
new PerformanceObserver((entryList) => {
const [navigationEntry] = entryList.getEntriesByType('navigation');
// 관련 타임스탬프 추출
const activationStart = navigationEntry.activationStart || 0;
const waitEnd = Math.max(
(navigationEntry.workerStart || navigationEntry.fetchStart) -
activationStart,
0,
);
const dnsStart = Math.max(
navigationEntry.domainLookupStart - activationStart,
0,
);
// 캐시 시간 계산
const cacheDuration = dnsStart - waitEnd;
// 결과 출력
console.log('%cTTFB cacheDuration', 'color: blue; font-weight: bold;');
console.log(cacheDuration);
}).observe({
type: 'navigation',
buffered: true
}); 높은 캐시 시간으로 인한 TTFB 문제 확인 방법
캐시 시간이 실제 사용자에게 미치는 영향을 파악하려면 CoreDash 같은 RUM 도구가 필요합니다. Real User Monitoring을 사용하면 Core Web Vitals를 상세하게 추적할 수 있습니다.
CoreDash에서 "Time to First Byte"로 이동한 뒤 세부 분석을 확인하십시오. TTFB를 구성하는 모든 하위 구간이 표시되며, 75번째 백분위수(p75) 기준 cacheDuration이 얼마나 소요되는지 정확히 확인할 수 있습니다.

서비스 워커 캐시 시간이 미치는 영향 최소화하기
서비스 워커를 사용할 때 TTFB를 최적화하는 방법은 다음과 같습니다.
- 시작 시간을 줄이기 위해 서비스 워커 스크립트의 복잡도를 최소화하십시오.
- 서비스 워커 내에 효율적인 캐싱 전략을 구현하십시오(탐색 요청에는 stale-while-revalidate 권장).
- 서비스 워커 설치 단계에서 핵심 리소스를 미리 캐싱(pre-caching)하는 방안을 고려하십시오.
- 서비스 워커가 사이트 TTFB에 미치는 영향을 정기적으로 모니터링하고 분석하십시오.
navigation preload를 사용하여 네트워크 요청이 서비스 워커 시작과 병렬로 진행되도록 하십시오. 이렇게 하면 서비스 워커 부팅 시간이 TTFB에 추가되는 것을 방지할 수 있습니다.
서비스 워커에서 navigation preload를 활성화하는 코드는 다음과 같습니다.
self.addEventListener('activate', (event) => {
event.waitUntil(
(async () => {
if (self.registration.navigationPreload) {
await self.registration.navigationPreload.enable();
}
})()
);
}); 제대로 구현하면 서비스 워커가 재방문자의 사이트 로딩 속도를 크게 높여줍니다. 하지만 잘못 구현하면 모든 페이지 로드마다 부팅 비용을 치러야 합니다.
추가 참고 자료: 최적화 가이드
관련 가이드:
- 103 Early Hints: 서버 응답이 준비되기 전에 핵심 리소스 로드를 시작하여 캐싱 전략을 보완합니다.
- 성능을 위한 Cloudflare 설정 가이드: CDN 엣지 캐싱, 캐시 규칙, 페이지 규칙을 설정하여 엣지 레벨에서 캐싱 전략을 최적화합니다.
TTFB 하위 구간: 전체 가이드
캐시 시간은 TTFB의 5개 하위 구간 중 하나입니다. 전체적인 그림을 파악하려면 다른 하위 구간도 살펴보십시오.
- TTFB 문제 진단 및 해결: 모든 TTFB 최적화의 진단 출발점입니다.
- Waiting Duration: 리디렉션, 브라우저 큐잉(queuing), HSTS 최적화.
- DNS Duration: DNS 제공업체 선택, TTL 설정, dns-prefetch.
- Connection Duration: TCP 핸드셰이크, TLS 최적화, HTTP/3, preconnect.
- Request Duration: 서버 처리 시간, 데이터베이스 쿼리, 백엔드 최적화.