노트

bfcache(뒤로·앞으로 가기 캐시)

Back/Forward Cache

프런트엔드#browser#performance · 연결된 개념 4개

쉽게 말하면

bfcache는 페이지를 떠날 때 버리지 않고 그 순간 그대로 얼려 두는 거예요. 뒤로 가기를 누르면 다시 불러오지 않고 녹이기만 하니까 거의 바로 화면이 나와요.

비유가 깨지는 곳 얼린 그대로 돌아오니 떠날 때 화면에 있던 애니메이션이나 모달도 멈춘 채 남아요. 임시 UI는 pagehide에서 정리하고, 오래된 데이터는 pageshow의 persisted를 보고 다시 불러와요.

bfcache(Back/Forward Cache)는 사용자가 페이지를 떠날 때 브라우저가 그 페이지를 없애지 않고 JS 힙까지 포함한 페이지 전체를 메모리에 얼려 두는 캐시다. 뒤로·앞으로 가기를 누르면 다시 로드하지 않고 얼려 둔 상태를 되살려 거의 즉시 보여 준다. 얼어 있는 동안 타이머와 프로미스 처리 같은 JS 실행은 멈춘다.

HTTP 캐시와 다른 점

| | HTTP 캐시 | bfcache | | --- | --- | --- | | 저장하는 것 | 응답(HTML·JS·이미지 파일) | 만들어진 페이지(DOM, JS 힙, 스크롤 위치) | | 돌아왔을 때 | 파일을 받아 페이지를 다시 만든다 | 멈췄던 페이지를 이어서 실행한다 | | 주체 | HTTP 헤더로 서버가 제어 | 브라우저가 스스로 판단 |

Next.js의 캐시나 라우터 캐시 같은 프레임워크 캐시와도 층이 다르다. 그쪽은 "페이지를 다시 만들 재료"를 들고 있고, bfcache는 "떠날 때의 화면 그 자체"를 들고 있다. 앱 안의 소프트 내비게이션은 문서를 떠나지 않으므로 bfcache가 끼어들 일이 없고, 다른 문서로 가는 하드 내비게이션 뒤에 돌아올 때 쓰인다.

감지와 정리: pageshow·pagehide

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // bfcache에서 복원됨: 오래된 데이터 갱신, 분석 이벤트 다시 보내기
  }
})
 
window.addEventListener('pagehide', () => {
  // 떠나기 직전: 임시 UI·애니메이션 정리
})
  • pageshow는 첫 로드와 복원 때 모두 발생하고, 복원이면 event.persisted가 true다
  • pagehide의 persisted는 "캐시에 넣으려 한다"는 뜻일 뿐 보장은 아니다
  • unload는 쓰지 않는다. 데스크톱 Chrome·Firefox에서는 unload 핸들러가 있으면 bfcache에 들어가지 못한다. 떠날 때의 정리는 pagehide에서 한다

복원하면 상태가 굳어 있다

얼린 시점의 화면이 그대로 돌아온다는 점이 함정이다. 축하 애니메이션 canvas가 떠 있는 채로 다른 사이트로 이동했다가 뒤로 오면, 멈춘 애니메이션이 화면에 남아 있다. 페이지를 새로 그리는 게 아니라 이어 붙이기 때문이다. 열려 있던 모달·토스트, 로딩 스피너, 장바구니 수량 같은 값도 마찬가지다. 그래서

  • 떠날 때 치워야 할 임시 UI는 pagehide에서 정리하고
  • 오래되면 안 되는 데이터는 pageshow의 persisted를 보고 다시 불러온다

막는 요인 (2026 기준)

  • unload 핸들러(데스크톱 Chrome·Firefox)
  • window.opener가 남아 있는 창, 진행 중인 fetch·XHR, 열려 있는 IndexedDB 연결 등
  • Cache-Control: no-store: 오랫동안 가장 큰 차단 사유였지만, Chrome은 2025년 봄(3~4월 100% 출시)에 안전한 경우 이런 페이지도 bfcache에 넣도록 바꿨다. 쿠키가 바뀌면 내보내고, 보관 시간도 일반 페이지(10분)보다 짧은 3분이다. 다른 브라우저는 여전히 막을 수 있다

Chrome DevTools의 Application → Back/forward cache에서 테스트를 돌리면 이 페이지가 들어갈 수 있는지, 못 들어가면 왜인지 알려 준다(크롬 개발자 도구 팁). 실제 방문에서 복원되지 않은 이유는 NotRestoredReasons API로 모을 수 있다.

출처: web.dev — Back/forward cache · Chrome for Developers — Enabling bfcache for Cache-Control: no-store

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • Activity와 숨겨진 라우트 보존

    Activity는 React 19.2의 컴포넌트로, UI를 언마운트하지 않고 display: none으로 숨기면서 state와 DOM(Document Object Model)을 보존한다. 숨기는 동안 Effect는 정리(cleanup)된다. 예전 이름은 Offscreen이다.

  • 버전 스큐와 정적 자산 오프로드

    버전 스큐(Version Skew)는 새 버전을 배포한 뒤에도 브라우저에 떠 있는 이전 빌드의 클라이언트가 새 서버와 통신하면서 생기는 불일치다. 대표 증상은 이전 HTML이 참조하던 JS 청크를 늦게 불러오려다 404가 나는 ChunkLoadError다.

  • "use cache"와 Cache Components

    'use cache'는 Next.js 16의 Cache Components 모델에서 async 함수나 컴포넌트의 반환값을 캐시하라고 명시하는 지시어(Directive)다. 아무것도 표시하지 않으면 매 요청 실행되고, 캐시하고 싶은 곳에만 직접 붙인다(옵트인, Opt-in).

  • requestAnimationFrame

    "다음 화면을 그리기 직전에 이 함수를 실행해 달라"고 브라우저에 예약하는 API. 콜백은 태스크 큐도 마이크로태스크 큐도 아닌 별도 목록에 들어가고, 이벤트 루프의 렌더링 단계에서 페인트 직전에 한꺼번에 실행된다.

  • 부분 사전 렌더링(PPR)

    PPR(Partial Prerendering)은 한 라우트를 빌드 때 만든 정적 셸(Static Shell)과 요청 때 채우는 동적 구멍으로 나눠, 셸은 즉시 보내고 구멍은 스트리밍으로 채우는 렌더링 모델이다. Next.js 16에서 Cache Components(cacheComponents: true)를 켜면 기본 동작이다(2026 기준).

보기 옵션