뒤로-앞으로 캐시

게시일: 2023년 5월 25일, 최종 업데이트: 2026년 7월 2일

뒤로-앞으로 캐시 (bfcache)는 즉시 뒤로 및 앞으로 탐색할 수 있는 브라우저 최적화 기능입니다. 특히 네트워크나 기기가 느린 사용자의 경우 탐색 환경이 크게 개선됩니다.

웹 개발자는 사용자가 이점을 누릴 수 있도록 bfcache에 맞게 페이지를 최적화하는 방법을 이해해야 합니다.

브라우저 호환성

Chrome(버전 96부터), Firefox, Safari를 비롯한 모든 주요 브라우저에는 bfcache가 포함되어 있습니다.

뒤로-앞으로 캐시 기본사항

뒤로/앞으로 캐시 (bfcache)를 사용하면 사용자가 이동할 때 페이지를 삭제하는 대신 삭제를 연기하고 JS 실행을 일시중지합니다. 사용자가 곧 다시 탐색하는 경우 페이지를 다시 표시하고 JS 실행을 일시중지 해제합니다. 이렇게 하면 사용자가 거의 즉시 페이지를 탐색할 수 있습니다.

웹사이트를 방문하여 다른 페이지로 이동하는 링크를 클릭했는데 원하는 페이지가 아니어서 뒤로 버튼을 클릭한 적이 얼마나 되나요? 이때 bfcache는 이전 페이지가 로드되는 속도에 큰 영향을 미칠 수 있습니다.

뒤로-앞으로 캐시가 사용 설정되지 않음 이전 페이지를 로드하기 위해 새 요청이 시작되며, 반복 방문에 맞게 페이지가 얼마나 잘 최적화되었는지에 따라 브라우저에서 방금 다운로드한 리소스의 일부 또는 전부를 다시 다운로드하고, 다시 파싱하고, 다시 실행해야 할 수도 있습니다.
뒤로-앞으로 캐시가 사용 설정된 경우 전체 페이지를 메모리에서 복원할 수 있으므로 네트워크로 이동할 필요가 없어 이전 페이지 로드가 사실상 즉시 이루어집니다.

bfcache가 탐색 속도를 얼마나 높일 수 있는지 알아보려면 bfcache가 작동하는 모습을 보여주는 이 동영상을 확인하세요.

bfcache를 사용하면 뒤로 및 앞으로 탐색 중에 페이지가 훨씬 빠르게 로드됩니다.

동영상에서 bfcache가 있는 예는 bfcache가 없는 예보다 훨씬 빠릅니다.

bfcache는 탐색 속도를 높일 뿐만 아니라 리소스를 다시 다운로드할 필요가 없으므로 데이터 사용량도 줄여줍니다.

Chrome 사용 데이터에 따르면 데스크톱의 10개 탐색 중 1개, 모바일의 5개 탐색 중 1개가 뒤로 또는 앞으로 탐색입니다. bfcache를 사용 설정하면 브라우저가 매일 수십억 개의 웹페이지에 대한 데이터 전송과 로드에 소요되는 시간을 없앨 수 있습니다.

'캐시'의 작동 방식

뒤로-앞으로 캐시에서 사용하는 '캐시'는 반복 탐색 속도를 높이는 데 자체적인 역할을 하는 HTTP 캐시와 다릅니다. 뒤로-앞으로 캐시는 JavaScript 힙을 비롯한 메모리의 전체 페이지 스냅샷인 반면 HTTP 캐시에는 이전에 이루어진 요청에 대한 응답만 포함됩니다. 페이지를 로드하는 데 필요한 모든 요청이 HTTP 캐시에서 처리되는 경우는 매우 드물기 때문에 bfcache 복원을 사용하는 반복 방문은 최적화가 잘 된 비 bfcache 탐색보다 항상 빠릅니다.

나중에 다시 사용 설정할 수 있도록 페이지를 고정하는 작업에는 진행 중인 코드를 가장 잘 보존하는 방법에 관한 복잡성이 수반됩니다. 예를 들어 페이지가 bfcache에 있는 동안 제한 시간이 도달하는 setTimeout() 호출은 어떻게 처리하나요?

브라우저는 JavaScript 작업 대기열의 거의 모든 대기 중인 작업을 포함하여 bfcache의 페이지에 대해 대기 중인 타이머나 해결되지 않은 약속을 일시중지하고 페이지가 bfcache에서 복원되면 작업 처리를 재개합니다.

타임아웃 및 프로미스와 같은 경우에는 위험이 비교적 낮지만, 다른 경우에는 혼란스럽거나 예기치 않은 동작으로 이어질 수 있습니다. 예를 들어 브라우저가 IndexedDB 트랜잭션의 일부로 필요한 작업을 일시중지하면 동일한 IndexedDB 데이터베이스에 여러 탭이 동시에 액세스할 수 있으므로 동일한 출처의 다른 열린 탭에 영향을 미칠 수 있습니다. 따라서 브라우저는 일반적으로 IndexedDB 트랜잭션 중간에 또는 다른 페이지에 영향을 줄 수 있는 API를 사용하는 동안 페이지를 캐시하려고 하지 않습니다.

다양한 API 사용이 페이지의 bfcache 적격성에 미치는 영향에 관한 자세한 내용은 bfcache에 맞게 페이지 최적화를 참고하세요.

bfcache 및 iframe

페이지에 삽입된 iframe이 포함된 경우 iframe 자체는 bfcache에 별도로 적합하지 않습니다. 예를 들어 iframe 내에서 다른 URL로 이동하면 이전 콘텐츠가 bfcache에 입력되지 않으며 뒤로 이동하면 브라우저가 기본 프레임이 아닌 iframe 내에서 '뒤로' 이동하지만 iframe 내의 뒤로 탐색은 bfcache를 사용하지 않습니다.

하지만 기본 프레임이 bfcache에서 복원되면 삽입된 iframe은 페이지가 bfcache에 들어갔을 때와 같이 복원됩니다.

삽입된 iframe이 이를 차단하는 API를 사용하는 경우 기본 프레임이 bfcache를 사용하지 못하도록 차단될 수도 있습니다. 기본 프레임에 설정된 권한 정책 또는 sandbox 속성을 사용하여 이를 방지할 수 있습니다.

bfcache 및 단일 페이지 앱 (SPA)

bfcache는 브라우저에서 관리하는 탐색과 함께 작동하므로 단일 페이지 앱 (SPA) 내의 '소프트 탐색'에는 작동하지 않습니다. 하지만 처음부터 앱을 다시 완전히 초기화하는 대신 SPA로 돌아갈 때는 bfcache가 도움이 될 수 있습니다.

bfcache를 관찰하는 API

bfcache는 브라우저에서 자동으로 실행하는 최적화이지만, 개발자가 bfcache가 언제 발생하는지 알아야 페이지를 최적화하고 그에 따라 측정항목이나 성능 측정을 조정할 수 있습니다.

bfcache를 관찰하는 데 사용되는 기본 이벤트는 대부분의 브라우저에서 지원되는 페이지 전환 이벤트 pageshowpagehide입니다.

최신 페이지 수명 주기 이벤트인 freezeresume도 페이지가 bfcache에 들어가거나 bfcache에서 나올 때, 그리고 CPU 사용량을 최소화하기 위해 백그라운드 탭이 고정되는 등 기타 상황에서 디스패치됩니다. 이 이벤트는 Chromium 기반 브라우저에서만 지원됩니다.

페이지가 bfcache에서 복원되는 시점 관찰

pageshow 이벤트는 페이지가 처음 로드될 때 load 이벤트 직후에 발생하며 언제든 페이지가 bfcache에서 복원될 때 발생합니다. pageshow 이벤트에는 persisted 속성이 있습니다. 이 속성은 페이지가 bfcache에서 복원된 경우 true이고 그렇지 않은 경우 false입니다. persisted 속성을 사용하여 일반 페이지 로드와 bfcache 복원을 구분할 수 있습니다. 예를 들면 다음과 같습니다.

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    console.log('This page was restored from the bfcache.');
  } else {
    console.log('This page was loaded normally.');
  }
});

Page Lifecycle API를 지원하는 브라우저에서는 페이지가 bfcache에서 복원될 때 (pageshow 이벤트 바로 전)와 사용자가 고정된 백그라운드 탭을 다시 방문할 때 resume 이벤트가 발생합니다. 페이지가 고정된 후 (bfcache의 페이지 포함) 페이지의 상태를 업데이트하려면 resume 이벤트를 사용할 수 있지만, 사이트의 bfcache 적중률을 측정하려면 pageshow 이벤트를 사용해야 합니다. 경우에 따라 두 가지를 모두 사용해야 할 수도 있습니다.

bfcache 측정 권장사항에 대한 자세한 내용은 bfcache가 분석 및 성능 측정에 미치는 영향을 참고하세요.

페이지가 bfcache에 들어가는 시점 관찰

pagehide 이벤트는 페이지가 언로드되거나 브라우저가 페이지를 bfcache에 넣으려고 할 때 발생합니다.

pagehide 이벤트에는 persisted 속성도 있습니다. false인 경우 해당 페이지가 bfcache에 진입하지 않을 것이라고 확신할 수 있습니다. 하지만 persistedtrue라고 해서 페이지가 캐시된다고 보장할 수는 없습니다. 이는 브라우저가 페이지를 캐시하려고 하지만 캐시를 불가능하게 하는 다른 요인이 있을 수 있음을 의미합니다.

window.addEventListener('pagehide', (event) => {
  if (event.persisted) {
    console.log('This page *might* be entering the bfcache.');
  } else {
    console.log('This page will unload normally and be discarded.');
  }
});

마찬가지로 persistedtrue인 경우 freeze 이벤트는 pagehide 이벤트 직후에 발생하지만 이는 브라우저가 페이지를 캐시하려고 의도한다는 의미일 뿐입니다. 하지만 나중에 설명된 여러 이유로 인해 여전히 삭제해야 할 수 있습니다.

bfcache에 맞게 페이지 최적화

모든 페이지가 bfcache에 저장되는 것은 아니며, 페이지가 저장되더라도 무기한으로 유지되지는 않습니다. 개발자가 캐시 적중률을 극대화하려면 페이지가 bfcache에 적합한지 (또는 적합하지 않은지) 파악하는 것이 중요합니다.

다음 섹션에서는 브라우저가 페이지를 캐시할 가능성을 최대한 높이기 위한 권장사항을 간략하게 설명합니다.

unload 이벤트는 사용하지 마세요.

모든 브라우저에서 bfcache를 최적화하는 가장 중요한 방법은 unload 이벤트를 사용하지 않는 것입니다. 언제든지

unload 이벤트는 bfcache보다 먼저 발생하며 인터넷의 많은 페이지가 unload 이벤트가 발생한 후에는 페이지가 계속 존재하지 않는다는 (합리적인) 가정하에 작동하므로 브라우저에 문제가 됩니다. 이러한 페이지는 사용자가 탐색을 종료할 때마다 unload 이벤트가 발생한다는 가정하에 또한 빌드되었기 때문에 문제가 발생합니다. 이는 더 이상 사실이 아니며 오랫동안 사실이 아니었습니다.

따라서 브라우저는 사용자 환경을 개선할 수 있지만 페이지가 깨질 위험도 있는 것 중에서 선택해야 하는 딜레마에 직면합니다.

데스크톱에서 Chrome과 Firefox는 unload 리스너를 추가하는 경우 페이지가 bfcache에 적합하지 않도록 선택했습니다. 이는 위험이 적지만 많은 페이지가 bfcache에서 제외됩니다. Safari는 unload 이벤트 리스너가 있는 일부 페이지를 캐시하려고 하지만 잠재적인 중단을 줄이기 위해 사용자가 탐색할 때는 unload 이벤트를 실행하지 않으므로 이벤트가 매우 불안정합니다.

모바일에서는 Chrome과 Safari가 unload 이벤트 리스너가 있는 페이지를 캐시하려고 시도합니다. unload 이벤트가 모바일에서 항상 매우 불안정했기 때문에 손상 위험이 낮기 때문입니다. Firefox는 unload를 사용하는 페이지를 뒤로-앞으로 캐시를 사용할 수 없는 페이지로 취급합니다. 단, 모든 브라우저가 WebKit 렌더링 엔진을 사용하므로 Safari처럼 작동하는 iOS에서는 예외입니다.

unload 이벤트를 사용하는 대신 pagehide 이벤트를 사용하세요. pagehide 이벤트는 unload 이벤트가 발생하는 모든 경우에 발생하며 페이지가 bfcache에 배치될 때도 발생합니다.

실제로 Lighthouse에는 no-unload-listeners 감사가 있으며, 페이지의 JavaScript (서드 파티 라이브러리의 JavaScript 포함)가 unload 이벤트 리스너를 추가하는 경우 개발자에게 경고합니다.

신뢰할 수 없고 bfcache의 성능에 영향을 미치기 때문에 Chrome에서는 unload 이벤트를 지원 중단하려고 합니다.

권한 정책을 사용하여 페이지에서 로드 취소 핸들러가 사용되지 않도록 방지

unload 이벤트 핸들러를 사용하지 않는 사이트는 권한 정책을 사용하여 이러한 핸들러가 추가되지 않도록 할 수 있습니다.

Permissions-Policy: unload=()

또한 서드 파티 또는 확장 프로그램이 언로드 핸들러를 추가하고 사이트에서 bfcache를 사용할 수 없게 만들어 사이트 속도를 저하시키는 것을 방지합니다.

beforeunload 리스너는 조건부로만 추가

beforeunload 이벤트는 최신 브라우저의 bfcache에서 페이지를 bfcache에 부적합하게 만들지 않지만 이전에는 그랬고 여전히 신뢰할 수 없으므로 꼭 필요한 경우가 아니면 사용하지 마세요.

하지만 unload 이벤트와 달리 beforeunload에는 합법적인 용도가 있습니다. 예를 들어 사용자가 페이지를 나가면 저장되지 않은 변경사항이 손실된다는 경고를 표시하려는 경우입니다. 이 경우 사용자가 저장되지 않은 변경사항이 있는 경우에만 beforeunload 리스너를 추가한 다음 저장되지 않은 변경사항이 저장된 후 즉시 삭제하는 것이 좋습니다.

금지사항
window.addEventListener('beforeunload', (event) => {
  if (pageHasUnsavedChanges()) {
    event.preventDefault();
    return event.returnValue = 'Are you sure you want to exit?';
  }
});
이 코드는 무조건 beforeunload 리스너를 추가합니다.
권장사항
function beforeUnloadListener(event) {
  event.preventDefault();
  return event.returnValue = 'Are you sure you want to exit?';
};

// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
  window.addEventListener('beforeunload', beforeUnloadListener);
});

// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
  window.removeEventListener('beforeunload', beforeUnloadListener);
});
이 코드는 필요한 경우에만 beforeunload 리스너를 추가하고 필요하지 않은 경우에는 삭제합니다.

Cache-Control: no-store 사용 최소화

Cache-Control: no-store는 웹 서버가 응답에 설정할 수 있는 HTTP 헤더로, 브라우저에 HTTP 캐시에 응답을 저장하지 않도록 지시합니다. 로그인 뒤에 있는 페이지와 같이 민감한 사용자 정보가 포함된 리소스에 사용됩니다.

뒤로-앞으로 캐시는 HTTP 캐시가 아니지만, 과거에는 Cache-Control: no-store가 하위 리소스가 아닌 페이지 리소스 자체에 설정된 경우 브라우저에서 페이지를 뒤로-앞으로 캐시에 저장하지 않도록 선택했으므로 Cache-Control: no-store를 사용하는 페이지는 뒤로-앞으로 캐시를 사용할 수 없을 수 있습니다. 개인 정보 보호 방식으로 Chrome의 이 동작을 변경하기 위한 작업이 진행 중입니다.

Cache-Control: no-store는 bfcache에 대한 페이지의 자격을 제한하므로 캐싱이 적절하지 않은 민감한 정보가 포함된 페이지에만 설정해야 합니다.

항상 최신 콘텐츠를 제공해야 하고 콘텐츠에 민감한 정보가 포함되지 않은 페이지의 경우 Cache-Control: no-cache 또는 Cache-Control: max-age=0를 사용하세요. 이러한 지시문은 콘텐츠를 제공하기 전에 콘텐츠를 재검증하도록 브라우저에 지시하며 페이지의 bfcache 자격 요건에는 영향을 미치지 않습니다.

페이지가 bfcache에서 복원되면 HTTP 캐시가 아닌 메모리에서 복원됩니다. 따라서 Cache-Control: no-cache 또는 Cache-Control: max-age=0과 같은 지시어가 고려되지 않으며 콘텐츠가 사용자에게 표시되기 전에 재검증이 발생하지 않습니다.

하지만 bfcache 복원은 즉시 이루어지고 페이지가 bfcache에 오래 머물지 않으므로 콘텐츠가 오래되었을 가능성은 낮기 때문에 여전히 더 나은 사용자 환경일 수 있습니다. 하지만 콘텐츠가 매분 변경되는 경우 다음 섹션에 설명된 대로 pageshow 이벤트를 사용하여 업데이트를 가져올 수 있습니다.

bfcache 복원 후 비활성 데이터 또는 민감한 정보 업데이트

사이트에서 사용자 상태(특히 민감한 사용자 정보)를 유지하는 경우 페이지가 bfcache에서 복원된 후 해당 데이터를 업데이트하거나 삭제해야 합니다.

예를 들어 사용자가 결제 페이지로 이동한 다음 장바구니를 업데이트하는 경우 bfcache에서 오래된 페이지가 복원되면 뒤로 탐색 시 오래된 정보가 노출될 수 있습니다.

더 심각한 예는 사용자가 공용 컴퓨터에서 사이트에서 로그아웃한 후 다음 사용자가 뒤로 버튼을 클릭하는 경우입니다. 이로 인해 사용자가 로그아웃할 때 삭제되었다고 생각한 비공개 데이터가 노출될 수 있습니다.

이러한 상황을 방지하려면 event.persistedtrue인 경우 pageshow 이벤트 후 항상 페이지를 업데이트하는 것이 좋습니다.

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Do any checks and updates to the page
  }
});

콘텐츠를 인플레이스로 업데이트하는 것이 가장 좋지만, 일부 변경사항의 경우 전체 새로고침을 강제해야 할 수도 있습니다. 다음 코드는 pageshow 이벤트에서 사이트별 쿠키의 존재를 확인하고 쿠키가 없으면 다시 로드합니다.

window.addEventListener('pageshow', (event) => {
  if (event.persisted && !document.cookie.match(/my-cookie)) {
    // Force a reload if the user has logged out.
    location.reload();
  }
});

새로고침은 기록을 유지하여 앞으로 이동할 수 있다는 장점이 있지만 경우에 따라 리디렉션이 더 적절할 수 있습니다.

광고 및 bfcache 복원

게시자는 뒤로/앞으로 탐색 시마다 광고를 새로고침할 수 있습니다. Cache-Control: no-store를 사용하여 페이지가 뒤로-앞으로 캐시를 사용할 수 없도록 하는 것은 사용자 성능에 해를 끼치는 잘못된 접근 방식입니다.

대신 페이지에서 bfcache를 사용할 수 있도록 유지하고 광고만 새로고침할 수 있습니다. Google 게시자 태그 (GPT)와 같은 일부 광고 태그 라이브러리는 사용자가 bfcache 복원을 통해 페이지로 돌아올 때 활성 상태로 조회된 광고 슬롯을 자동으로 새로고침합니다. 광고 태그 라이브러리가 이 작업을 수행하지 않고 광고를 새로고침하려면 pageshow 이벤트를 사용하여 bfcache 복원을 감지하고 광고 새로고침을 트리거하면 됩니다.

window.opener 참조 방지

이전 브라우저에서는 rel="noopener"를 지정하지 않고 target=_blank가 있는 링크에서 window.open()를 사용하여 페이지를 연 경우 여는 페이지에 열린 페이지의 창 객체에 대한 참조가 있었습니다.

보안 위험 외에도 null이 아닌 window.opener 참조가 있는 페이지는 bfcache에 안전하게 배치할 수 없습니다. bfcache에 배치하면 해당 페이지에 액세스하려는 페이지가 중단될 수 있기 때문입니다.

따라서 window.opener 참조를 만들지 않는 것이 좋습니다. 가능한 경우 언제든지 rel="noopener"를 사용하여 이를 수행할 수 있습니다 (이제 모든 최신 브라우저에서 기본값임). 사이트에서 창을 열고 window.postMessage()를 통해 창을 제어하거나 창 객체를 직접 참조해야 하는 경우 열린 창과 오프너 모두 bfcache를 사용할 수 없습니다.

사용자가 다른 곳으로 이동하기 전에 열려 있는 연결 닫기

앞서 언급했듯이 페이지가 bfcache에 보관되면 예약된 모든 JavaScript 작업이 일시중지되고 페이지가 캐시에서 삭제되면 다시 시작됩니다.

이러한 예약된 JavaScript 작업이 DOM API 또는 현재 페이지에만 격리된 다른 API에만 액세스하는 경우 페이지가 사용자에게 표시되지 않는 동안 이러한 작업을 일시중지해도 문제가 발생하지 않습니다.

하지만 이러한 작업이 동일한 출처의 다른 페이지에서도 액세스할 수 있는 API (예: IndexedDB, Web Locks, WebSockets)에 연결되어 있다면 문제가 될 수 있습니다. 이러한 작업을 일시중지하면 다른 탭의 코드가 실행되지 않을 수 있기 때문입니다.

따라서 일부 브라우저는 다음 시나리오에서 페이지를 bfcache에 넣으려고 시도하지 않습니다.

  • IndexedDB 연결이 열려 있는 페이지
  • 진행 중인 fetch() 또는 XMLHttpRequest가 있는 페이지
  • WebSocket 또는 WebRTC 연결이 열려 있는 페이지 Chrome (149 기준)과 Safari는 WebSocket 열기를 차단하지 않지만 다른 브라우저는 차단합니다.

페이지에서 이러한 API를 사용하는 경우 pagehide 또는 freeze 이벤트 중에 연결을 닫고 관찰자를 삭제하거나 연결을 해제하는 것이 좋습니다. 이렇게 하면 브라우저가 열려 있는 다른 탭에 영향을 미칠 위험 없이 페이지를 안전하게 캐시할 수 있습니다.

그런 다음 페이지가 bfcache에서 복원되면 pageshow 또는 resume 이벤트 중에 이러한 API를 다시 열거나 다시 연결하거나 error 또는 close 이벤트를 사용하여 항상 자동으로 다시 열 수 있습니다. 여러 이벤트를 사용하는 경우 여러 연결을 열지 마세요.

다음 예시는 pagehide 이벤트 리스너에서 열린 연결을 닫아 IndexedDB를 사용하는 페이지가 bfcache에 적합하도록 하는 방법을 보여줍니다.