검색페이지로 이동GitHub링크로 이동이메일보내기

BFCache

웹 브라우저의 Back-Forward Cache(BFCache)에 대해 알아보고, 이를 활용하여 웹 애플리케이션의 성능을 향상시키는 방법

Back-Forward Cache(BFCache)

BFCache는 즉시 앞뒤로 탐색할 수 있는 브라우저 최적화 기능이다. 특히 네트워크나 기기 속도가 느린 사용자의 경우 탐색 환경이 크게 개선된다. 브라우저를 탐색하다가 뒤로가기나 앞으로가기를 클릭시, 또는 이전에 방문했던 링크를 재방문하는경우 페이지가 다시 로드되지않고, 빠르게 보여지는 경우가 있다.

BFCache 작동 방식

  • BFCache 사용 안함 : 페이지를 벗어나면 페이지 상태가 메모리에서 해제되고, 다시 방문할 때 네트워크 요청을 통해 페이지를 다시 로드한다.
  • BFCache 사용 : 페이지를 벗어나더라도 페이지 상태가 메모리에 유지되어, 다시 방문할 때 네트워크 요청 없이 즉시 페이지를 복원한다. BFCache는 이전 탐색한 페이지를 재탐색하는 속도도 높을 뿐만 아니라, 네트워크 요청을 줄여 데이터 사용량도 절감할 수 있다.

HTTP캐시와의 차이점

BFCache는 HTTP캐시와는 다르게 동작한다.

HTTP캐시란 브라우저 또는 중간 프록시가 HTTP 응답을 저장하여 같은 리소스에 대한 후속 요청 시 네트워크 요청을 줄이는 메커니즘이다. HTTP캐시는 주로 정적 리소스(이미지, CSS, JavaScript 파일 등)에 적용된다. HTTP Header의 Cache-Control, ETag, Last-Modified 등의 지시어를 통해 캐싱 동작을 제어할 수 있다.

반면에, BFCache는 브라우저가 JavaScript의 힙을 포함하여 메모리에 있는 전체 페이지를 스냅샷으로 저장한다. 사용자가 뒤로가기나 앞으로가기를 클릭할 때, 브라우저는 이 스냅샷을 사용하여 페이지를 즉시 복원한다. BFCache는 페이지의 전체 상태를 저장하므로, HTTP캐시와 달리 동적 콘텐츠나 상태도 유지된다.

BFCache 조건

브라우저의 종료와 버전에 따라 BFCache의 동작이 다를 수 있다. 일반적으로 다음과 같은 조건을 충족해야 BFCache가 활성화된다.

  • unload 이벤트 리스너가 없어야 한다.
    • unload 이벤트는 페이지가 완전히 종료됨을 의미해서 브라우저가 스냅샷을 저장할 수 없다.
  • Cache-Control: no-store 가 없어야 한다.
    • 웹 서버가 응답에 설정할 수 있는 이 헤더는 HTTP 헤더로, 브라우저의 응답을 HTTP 헤더에 캐시에 저장하지 말라 지시한다.
  • iframe 내에서 로드된 페이지는 BFCache에 저장되지 않는다.
    • 보안 및 성능상의 이유로, iframe 내에서 로드된 페이지는 BFCache에 저장되지 않는다.

BFCache 감지

  • 페이지가 BFCache에서 복원되는 시점 감지
    • pageshow 이벤트는 페이지가 처음 로드될 때, load 이벤트 직후와 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.');
  }
});
  • 페이지가 BFCache에 저장되는 시점 감지
    • pagehide 이벤트는 페이지가 벗어날 때 발생하며, BFCache에 저장될 때와 완전히 언로드될 때 모두 발생한다.
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.');
  }
});

BFCache 최적화

  • unload 이벤트 리스너 제거
    • 페이지에서 unload 이벤트 리스너를 제거하여 BFCache가 활성화될 수 있도록 한다.
    • 대신 pagehide 이벤트를 사용하여 페이지가 벗어날 때 필요한 정리 작업을 수행한다.
  • Cache-Control 헤더 조정
    • 서버에서 응답 헤더에 Cache-Control: no-store를 설정하지 않도록 한다.
  • iframe 사용 최소화
    • 가능하면 iframe 내에서 페이지를 로드하지 않도록 한다.
  • 열려있는 리소스 정리
    • IndexedDB, 진행 중인 fetch() 또는 XMLHttpRequest, WebSocket, WebRTC 연결이 있는 페이지는 BFCache에 저장되지 않을 수 있다.
    • 이러한 리소스를 정리하거나 닫는 코드를 pagehide 이벤트 리스너에 추가한다.
window.addEventListener('pagehide', (event) => {
  if (event.persisted) {
        // IndexedDB 연결 닫기
        if (db) {
          db.close();
        }
        // 진행 중인 fetch 요청 취소
        if (controller) {
            controller.abort();
        }
        // WebSocket 연결 닫기
        if (socket) {
            socket.close();
        }
    }
});
  • window.opener
    • window.open() 또는 a태그의 target="_blank" 속성으로 새 창/탭에서 열린 페이지는 자신의 부모 페이지에 대한 참조 window.opener를 가지는데, 이것이 null이 아닐 경우 bfcache에 부적격한 것으로 판단된다.
    • 따라서 window.opener를 null, 또는 참를 만들지 않는게 좋다.
    • 사이트에서 창을 열고 window.postMessage()를 통해 제어하거나 창 객체를 직접 참조해야 하는 경우 열린 창이나 opener는 BFCache를 사용할 수 없다.

마치며

아직 직접적으로 BFCache를 활용해보거나, 관련 이슈를 접해본적이 없어서 이번 포스트를 통해 개념적으로나마 이해할 수 있었다. React와 같은 SPA 프레임워크 내에서는 기본적으로 동작하지 않는다, 하지만 SPA에서 다른 페이지로 이동했다가, 뒤로 돌아오는 경우에는 BFCache가 동작할 수 있다. 따라서, useEffect와 같은 마운트이펙트는 다시 실행되지 않기 때문에, BFCache에서 복원될 때 필요한 작업이 있다면 pageshow/pagehide 이벤트를 활용하는 것이 좋다.

  • 광고·추천 컴포넌트의 단발 리프레시
  • 세션 만료/권한 재체크
  • 서버 상태(stale 가능)만 부분 invalidate