뒤로 가기 속도가 느린 이유, bfcache가 막혀서입니다
사용자가 브라우저 ‘뒤로 가기’를 눌렀을 때 화면이 즉시 뜨지 않고 다시 로딩된다면, bfcache(뒤로/앞으로 캐시)가 비활성화되었을 가능성이 큽니다. 이 순간 이탈률이 높아지고 세션당 페이지뷰가 줄어들 수 있습니다.
bfcache는 이전 페이지의 상태를 통째로 보관해 즉시 복원합니다. 리스트 → 상세 → 뒤로 가기 같은 흐름에서 체감 속도를 크게 올려줍니다.
문제는 오래된 습관대로 unload 이벤트를 쓰거나, 상태 정리를 잘못하면 브라우저가 bfcache를 아예 끊어버린다는 점입니다. 이 글은 unload를 지우고 pagehide/pageshow로 전환하며, DevTools로 원인을 정확히 검사하는 체크리스트를 제공합니다.
> 안티패턴: 분석 스크립트나 정리 코드를 unload에 묶어두면 bfcache가 차단됩니다. 운영 중이라면 즉시 사용 여부를 점검하세요.
bfcache가 막히는 전형적 상황, 짧은 사례로 이해하기
SPA가 아닌 일반 목록→상세 흐름에서 뒤로 가기가 느리다면, 페이지 이탈 시 실행한 코드가 bfcache를 차단했을 가능성이 높습니다. 특히 이벤트 핸들러와 네트워크 전송이 문제를 자주 만듭니다.
예를 들어 결제 완료 화면에서 방문 종료를 추적하려고 unload에 비동기 전송을 붙이면, 브라우저는 페이지 상태 보관을 위험하다고 보고 bfcache에 넣지 않습니다. 결과적으로 다음에 뒤로 가기 시 전체 리로드가 발생해 체감이 느려집니다.
실무에서 자주 보이는 원인들은 다음과 같습니다.
- unload에서 통계 전송(fetch, sync XHR, navigator.sendBeacon 오용)
- beforeunload로 불필요한 이탈 경고 띄우기
- window.close 같은 강제 종료 시도
- pagehide에서 persist 고려 없이 상태 초기화 남발
대안은 종료 시그널을 pagehide로 받고, 복귀 시점에는 pageshow에서 상태를 복원하는 것입니다. 흐름은 `pagehide(정리) -> bfcache 저장 -> pageshow(복원)`처럼 단순하게 두면 됩니다.
> 팁: 분석 전송은 navigator.sendBeacon을 pagehide와 함께 사용하고, pageshow에서 중복 전송 방지를 확인하세요. 네트워크 혼잡 시에도 UI 복원이 늦어지지 않습니다.
bfcache 체크리스트 한눈에
- unload 제거: unload 코드는 지우고 저장·통계는 pagehide로 옮깁니다. beforeunload는 꼭 필요한 이탈 확인에만 둡니다.
- pagehide 처리: 화면이 숨길 때 저장·전송을 합니다. e.persisted로 초기화 필요 여부를 나눕니다. 긴 비동기는 먼저 sendBeacon을 검토하세요.
- pageshow 복원: 뒤로/앞으로 이동 시 persisted 값으로 초기화 스킵을 판단합니다. 타이머·웹소켓만 선택해 다시 연결합니다.
- DevTools 점검: Performance의 Back/Forward Cache와 Not Restored Reason을 확인해 코드 위치와 원인을 연결합니다.
> 실무 팁: 긴 fetch는 복원을 막을 수 있습니다. 종료 전송은 작게, 시간 제한을 두고, sendBeacon 우선을 권장합니다.
아래 코드는 unload를 빼고 pagehide/pageshow로 나눈 최소 예시입니다. API와 경로는 서비스에 맞게 바꾸세요.
<script>
window.addEventListener('pagehide', (e) => {
sessionStorage.setItem('lastPath', location.pathname);
const data = JSON.stringify({ path: location.pathname, ts: Date.now() });
navigator.sendBeacon('/analytics/leave', new Blob([data], { type: 'application/json' }));
if (!e.persisted) {
window.myHeavyCache?.clear?.();
}
});
window.addEventListener('pageshow', (e) => {
if (e.persisted) {
window.syncBadgeCount?.();
} else {
window.initPage?.();
}
});
</script>
정리: pagehide에서는 저장·전송만 가볍게 처리하고 큰 정리는 조건부로 합니다. pageshow에서는 persisted 분기로 복원을 최소화합니다.
흔한 실수와 선택 기준, 무엇을 지우고 어디로 옮길까
unload를 지우면 끝이 아닙니다. 같은 동작을 pagehide/pageshow에 어떻게 나눌지가 품질을 좌우합니다.
자주 생기는 실수는 다음과 같습니다.
- pagehide에서 e.persisted 무시하고 전역 초기화로 상태를 지워버림
- 긴 fetch/동기 XHR로 종료 전송을 시도해 복원을 불가능하게 만듦
- pageshow에서 매번 타이머·웹소켓을 재연결해 중복 작업 발생
- beforeunload로 과도한 이탈 경고를 띄워 사용자 경험을 해침
선택 기준은 단순합니다.
- 종료 기록·짧은 저장: pagehide에서 sendBeacon 우선
- UI/메모리 해제: pagehide에서만 처리, persisted일 때는 “보존”
- 데이터 초기화: pageshow에서 persisted=false일 때만 전체 리셋
- 실시간 연결: pageshow에서만 “필요한 것만” 재연결
> 실무 팁: “상태는 보수적으로 보존하고, 연결은 필요할 때 최소로 재수립”이 기본 원칙입니다. 복원 흐름은 pagehide -> bfcache 보관 -> pageshow의 3단계로 생각하세요.
비교 관점으로 보면, unload는 “떠나며 정리” 방식이라 복원 자체를 막기 쉽고, pagehide/pageshow는 “숨기고 다시 보이기” 모델이라 뒤로 가기 속도에 유리합니다. 다만 네트워크 전송은 짧게, 초기화는 조건부로 제한하지 않으면 오히려 체감 속도를 떨어뜨릴 수 있습니다.
지금 할 일 6단계로 끝내기
가장 빠른 효과는 unload 제거와 pagehide/pageshow 전환입니다. 아래 순서대로 적용하면 뒤로 가기 체감 속도를 단기간에 개선할 수 있습니다.
- 코드 검색: unload, beforeunload, sync XHR, 긴 fetch 사용 위치 찾기
- 제거/전환: unload 삭제 → 종료 로직을 pagehide로 이동
- 분기 처리: pagehide/pageshow에서 e.persisted 기준으로 초기화 여부 결정
- 전송 방식: 종료 기록은 sendBeacon 우선, 긴 네트워크 작업은 시간 제한
- 복원 범위: pageshow에서 타이머·소켓 등 필요한 것만 선택 재연결
- 검증: DevTools Performance → Back/Forward Cache로 원인과 복원 여부 확인
> 실무 팁: “끝나길 기다리는 비동기”는 bfcache의 적입니다. 종료 단계에서는 작은 페이로드와 짧은 타임아웃을 기본값으로 두세요.
우선순위는 검색→삭제→대체→검증 흐름입니다. 검색으로 영향 코드를 모으고, 최소 변경으로 대체한 뒤 DevTools에서 Not Restored Reason을 확인하세요.
적용 후에는 상세→뒤로 가기 왕복 속도를 실제 페이지에서 체크합니다. 느리다면 네트워크 전송 크기나 재연결 범위를 한 번 더 줄여보는 것이 좋습니다.
'프로그래밍' 카테고리의 다른 글
| OffscreenCanvas + Web Workers + WebAssembly SIMD로 고FPS 차트·시뮬레이션 렌더링 시작하기 (0) | 2026.07.10 |
|---|---|
| Speculation Rules API 실전 가이드: prerender/prefetch 전략과 안전 가드 정리 (0) | 2026.07.10 |
| Tauri 2 자동 업데이트 채널 설계: 서명·릴리스 파이프라인·롤백 전략 가이드 (0) | 2026.06.26 |
| React Compiler 도입 체크리스트: memo/useMemo 제거 기준과 회귀 테스트 전략 (0) | 2026.06.26 |
| OpenAI Realtime API(WebRTC)로 브라우저 음성 챗봇 구축: 에페메럴 키와 Agents SDK 실전 가이드 (0) | 2026.06.25 |