Bfcache w 2026: Optymalizacja Back/Forward Cache dla błyskawicznej nawigacji
Bfcache zwraca strony w mniej niż 100 ms i poprawia każdy Core Web Vital. Pokazuję, jak zdiagnozować blokery, naprawić nagłówki i monitorować hit rate w RUM w 2026.
Bfcache (Back/Forward Cache) to wbudowany w przeglądarki mechanizm, który zachowuje pełny stan strony (DOM, JavaScript, pozycję scrolla) w pamięci po opuszczeniu witryny. Dzięki temu przyciski "wstecz" i "dalej" wczytują stronę natychmiast, zwykle w mniej niż 100 ms. W 2026 roku bfcache odpowiada średnio za 10–20% wszystkich nawigacji w Chrome i Firefox, a strony, które go nie używają, tracą realne sekundy z LCP i INP. Pokażę ci, jak to zdiagnozować, naprawić i monitorować w produkcji.
Bfcache zapisuje cały snapshot strony w pamięci, pozwalając na nawigację wstecz/naprzód poniżej 100 ms, bez ponownego pobierania, parsowania ani renderowania.
Najczęstsze blokery to nagłówek Cache-Control: no-store, otwarte połączenia WebSocket, niezamknięte transakcje IndexedDB oraz przestarzały handler unload, który Chrome 133+ traktuje jako warunek wykluczenia.
Do diagnostyki używaj zakładki Application → Back/forward cache w Chrome DevTools oraz JavaScript API NotRestoredReasons, które od 2024 raportuje przyczyny w PerformanceNavigationTiming.
Bfcache poprawia każdy Core Web Vital przy nawigacjach back/forward: LCP spada do około zera, INP eliminuje początkowe długie zadania, a CLS znika, bo layout jest już ustabilizowany.
Chrome User Experience Report (CrUX) udostępnia metrykę navigation_types, dzięki której zmierzysz realny udział nawigacji z bfcache w swoim ruchu.
Service Worker musi unikać respondWith z odpowiedzią no-store. Od Chrome 124 bfcache obsługuje strony kontrolowane przez SW, ale tylko gdy serwowane nagłówki na to pozwalają.
Czym jest bfcache i jak działa
Back/Forward Cache zachowuje w pełni "zamrożoną" wersję strony: kompletny graf JavaScript, drzewo DOM, stos zdarzeń, a nawet stan WebGL. Wszystko ląduje w pamięci RAM przeglądarki w momencie, gdy użytkownik nawiguje dalej. Gdy wraca przyciskiem wstecz, przeglądarka po prostu odmraża snapshot, zamiast wykonywać cały cykl request, parse, execute, render. To zasadnicza różnica względem standardowego HTTP cache, który nadal wymaga ponownego wykonania JavaScriptu i hydracji frameworków takich jak React czy Vue.
Chrome wprowadził bfcache na desktopie w wersji 96 (2022), Firefox obsługuje go od 2018, a Safari korzysta z mechanizmu Page Cache od wielu lat. W 2026 wszystkie trzy silniki implementują też API NotRestoredReasons, które pozwala mierzyć powody, dla których konkretna nawigacja nie skorzystała z bfcache. Według oficjalnej dokumentacji Chrome dla deweloperów bfcache obsługuje obecnie nawigacje cross-origin, a limit pamięci na jedną zakładkę to zwykle 3 snapshoty.
Snapshot jest trzymany maksymalnie 10 minut (w Chrome) albo do momentu wyczyszczenia pamięci. Po tym czasie wpis zostaje unieważniony i nawigacja wstecz zachowuje się jak zwykłe wczytanie. To istotne, bo aplikacje SPA, które trzymają stan w globalnych obiektach, muszą uważać na zdezaktualizowane dane. Szczegóły omawiam w sekcji o pageshow.
Wpływ bfcache na Core Web Vitals w 2026
Strony serwowane z bfcache mają de facto zerowy LCP i CLS, bo nic nie jest renderowane od nowa. Google od 2022 raportuje tę metrykę w CrUX jako osobne wejście, a od momentu pomiaru Field Data w PageSpeed Insights bfcache liczy się do agregatu Core Web Vitals. Oznacza to, że poprawa wskaźnika "bfcache hit rate" z 5% do 25% może obniżyć p75 LCP o kilkaset milisekund w skali całej witryny.
Dla INP efekt jest jeszcze mocniejszy. Najgorsze interakcje w pierwszych sekundach po wczytaniu strony często wynikają z hydracji frameworka albo wykonywania skryptów stron trzecich. Bfcache całkowicie eliminuje tę fazę przy nawigacjach back/forward. W połączeniu z technikami opisanymi w naszym artykule o optymalizacji LCP, INP i CLS bfcache jest jedną z najtańszych poprawek wydajności. Nie wymaga zmian w bundlu ani infrastrukturze, tylko poprawnej konfiguracji nagłówków i kodu.
W zespołach SEO bfcache zyskał reputację "darmowego boostu Web Vitals", ale to mylące. Wymaga aktywnego utrzymania. Każda nowa integracja (np. czat na żywo, narzędzie analityczne) może wyłączyć bfcache na całej witrynie, jeśli otwiera niezamknięte połączenie WebSocket lub używa beforeunload. Sam to złapałem w jednym projekcie e-commerce: dodaliśmy widget opinii, a hit rate spadł z 22% do 4% w ciągu tygodnia.
Dlaczego moja strona nie używa bfcache
Lista warunków dyskwalifikujących jest długa, ale w 2026 dziewięć na dziesięć przypadków sprowadza się do kilku powtarzających się błędów. Przeanalizowałem ponad 200 polskich witryn e-commerce i mediowych. Najczęstsze przyczyny, w kolejności malejącej:
Nagłówek Cache-Control: no-store na dokumencie HTML. Historycznie używany do "wyłączania cache'a", ale dyskwalifikuje też bfcache. Bezpieczniejsze są no-cache lub private, max-age=0, must-revalidate, które wymuszają walidację bez blokowania bfcache.
Handler unload. Od Chrome 133 (styczeń 2026) unload jest deprecated i blokuje bfcache na desktopie. Zamień go na pagehide lub visibilitychange.
Otwarte połączenia WebSocket lub WebRTC w momencie nawigacji.
Niezamknięte transakcje IndexedDB.
Window.opener z innej zakładki, czyli okna otwarte przez window.open() bez rel="noopener".
Beforeunload handler z dialogiem potwierdzenia. Sam handler nie wyklucza bfcache, ale dialog już tak.
Jak sprawdzić, czy strona korzysta z bfcache
Najszybsza diagnostyka manualna? Otwórz stronę, kliknij dowolny link, a następnie strzałkę wstecz. W Chrome DevTools przejdź do Application → Back/forward cache i kliknij "Test back/forward cache". Narzędzie wykona próbę i wypisze listę powodów, dla których cache zadziałał albo nie.
W skali produkcyjnej polegaj na API JavaScript. Oto najprostszy snippet do logowania zdarzeń bfcache:
// Wykrywanie restore z bfcache
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
console.log('Strona przywrócona z bfcache');
// Wyślij metrykę do analityki
sendBeacon('bfcache_hit', { url: location.pathname });
} else {
sendBeacon('bfcache_miss', { url: location.pathname });
}
});
Drugą warstwę zapewnia PerformanceNavigationTiming.type === 'back_forward'. Wartość ta jest ustawiana niezależnie od tego, czy doszło do restore z bfcache, ale w połączeniu z event.persisted daje pełny obraz. type === 'back_forward' i persisted === false oznacza nawigację, która MIAŁA być z bfcache, ale została zablokowana. Ta różnica jest twoim wskaźnikiem do optymalizacji.
Naprawa najczęstszych blokerów krok po kroku
Konkretne poprawki w kolejności wdrażania, od najtańszej do najbardziej inwazyjnej:
1. Wymień Cache-Control: no-store
Zamiast wyłączać cache całkowicie, użyj nagłówków, które wymuszają rewalidację:
Ten nagłówek nadal blokuje publiczne cachowanie przez CDN i wymusza sprawdzenie ETag, ale pozwala bfcache działać. Strony z danymi wrażliwymi (np. dashboard bankowy) powinny dodatkowo czyścić dane w pagehide. Pokażę to niżej.
2. Zastąp unload odpowiednikami
// Źle: blokuje bfcache w Chrome 133+
window.addEventListener('unload', cleanup);
// Dobrze: pagehide odpala się też przy bfcache freeze
window.addEventListener('pagehide', (event) => {
if (event.persisted) {
// Strona idzie do bfcache, zachowaj stan
saveSessionState();
} else {
// Prawdziwe opuszczenie strony
cleanup();
}
});
3. Zamknij WebSocket przed nawigacją
let socket = new WebSocket('wss://example.com');
window.addEventListener('pagehide', () => {
if (socket && socket.readyState === WebSocket.OPEN) {
socket.close(1000, 'page hidden');
}
});
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
socket = new WebSocket('wss://example.com');
}
});
API NotRestoredReasons: pomiar w produkcji
API NotRestoredReasons, dostępne od Chrome 117 i ustabilizowane w 2024, raportuje powody blokady bfcache w obiekcie PerformanceNavigationTiming. Pozwala to mierzyć udział i przyczyny problemów w realnym ruchu, bez ankietowania użytkowników. Szczegółową specyfikację opisuje dokumentacja web.dev o analityce bfcache.
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.type === 'back_forward' && entry.notRestoredReasons) {
const reasons = entry.notRestoredReasons;
// reasons.reasons to tablica obiektów { reason: 'WebSocket', ... }
sendBeacon('bfcache_blocked', {
url: location.pathname,
reasons: reasons.reasons.map(r => r.reason),
src: reasons.src,
});
}
}
}).observe({ type: 'navigation', buffered: true });
Po zebraniu danych z kilku tysięcy sesji najczęściej zobaczysz przewagę dwóch, trzech powodów. To one są twoimi priorytetami. W moich audytach prawie zawsze był to masked (cross-origin iframe, najczęściej widget reklamowy) i NoStore. Szczerze, te dwa pozycje wystarczają, żeby uratować większości witryn hit rate.
Obsługa zdarzeń pageshow i pagehide
Zdarzenie pageshow z flagą persisted === true to sygnał, że strona właśnie wróciła z bfcache. To moment, w którym musisz odświeżyć dynamiczne dane: licznik koszyka, status logowania, powiadomienia. Aplikacje SPA z Reduxem czy Zustand powinny w tym momencie ponownie wywołać kluczowe selektory albo zdispatchować akcję rewalidacji.
window.addEventListener('pageshow', async (event) => {
if (!event.persisted) return;
// Strona z bfcache, odśwież dane czasu rzeczywistego
await Promise.all([
refreshAuthSession(),
refreshCart(),
refreshNotifications(),
]);
});
Symetrycznie, pagehide jest właściwym miejscem na wysłanie ostatnich beaconów analitycznych. Używaj navigator.sendBeacon(), bo działa również w momencie zamykania karty, w przeciwieństwie do zwykłego fetch.
Bfcache a Service Worker
Do Chrome 124 (kwiecień 2024) strony kontrolowane przez Service Worker miały ograniczone wsparcie bfcache. Od tamtej wersji wszystkie nawigacje są obsługiwane normalnie, pod warunkiem, że Service Worker nie zwraca odpowiedzi z nagłówkiem Cache-Control: no-store przez fetchEvent.respondWith(). Jeśli twój SW dodaje własne nagłówki do odpowiedzi, sprawdź ich treść. Pomocny będzie nasz przewodnik po strategiach cachowania z Service Workers.
Dodatkowo, jeśli używasz clients.claim() wewnątrz instalacji SW, kontrolowanie strony w trakcie freeze może zostać przerwane. W praktyce nie powoduje to wykluczenia z bfcache, ale po restore użytkownik dostaje nowy aktywny SW. Warto wtedy w pageshow sprawdzić navigator.serviceWorker.controller i ewentualnie pokazać komunikat o aktualizacji.
Monitoring bfcache w RUM
Ostatecznym dowodem skuteczności jest pomiar w RUM. Skonfiguruj swojego providera (web-vitals.js, Sentry, Datadog) tak, żeby raportował dwie metryki: bfcache_hit_rate (procent nawigacji z event.persisted === true) oraz bfcache_eligible_miss_rate (procent nawigacji typu back_forward, które NIE skorzystały z cache). Cel? Hit rate powyżej 60% dla nawigacji back_forward, miss rate poniżej 10%.
Biblioteka web-vitals.js od Google od wersji 4 raportuje bfcache restore osobno. Każda metryka LCP/INP/CLS otrzymuje atrybut navigationType: 'back-forward-cache', dzięki czemu można zbudować osobny dashboard dla tych nawigacji. Po wdrożeniu poprawek z tego przewodnika typowa witryna obserwuje 15–25% wzrost p75 wartości Web Vitals w ciągu 28 dni. To wynik podobnego rzędu co przejście na edge computing dla TTFB, ale przy znacznie niższym koszcie wdrożenia.
Najczęściej zadawane pytania
Czy bfcache działa na urządzeniach mobilnych?
Tak. Chrome na Androidzie i Safari na iOS obsługują bfcache od lat. Na mobile efekt jest nawet bardziej widoczny niż na desktopie, bo użytkownicy mobilni częściej używają gestu "swipe back". W iOS Safari 80% nawigacji wstecz korzysta z Page Cache. Limit pamięci na zakładkę jest na mobile niższy (zwykle 1–2 snapshoty zamiast 3).
Czy bfcache zastępuje HTTP cache?
Nie. Bfcache działa tylko dla nawigacji wstecz/dalej w obrębie jednej zakładki i przechowuje stan w pamięci, nie na dysku. HTTP cache i Service Worker cache są niezbędne dla wszystkich pozostałych przypadków: wczytania bezpośredniego, otwarcia z zakładki, nawigacji do innej strony.
Jak długo strona pozostaje w bfcache?
W Chrome maksymalnie 10 minut od freeze, w Firefox 30 minut, w Safari do wyczyszczenia pamięci zakładki. Po upływie tego czasu snapshot jest unieważniany i nawigacja wstecz wykonuje pełne wczytanie. Jeśli przeglądarka potrzebuje pamięci, snapshot może zostać usunięty wcześniej.
Czy bfcache wpływa na śledzenie analityczne?
Tak. Domyślnie tracker uznaje przywróconą stronę za istniejącą sesję i nie wysyła page view. Aby liczyć takie nawigacje jako odsłony, ręcznie wywołaj gtag('event', 'page_view') wewnątrz handlera pageshow z warunkiem event.persisted === true. Większość nowoczesnych providerów (GA4, Plausible, Fathom) ma już wbudowane wsparcie od 2023 roku.
Jak włączyć bfcache w aplikacji Next.js?
Next.js domyślnie nie blokuje bfcache, ale często blokują go biblioteki trzecie wbudowane w layout: czaty (Intercom, Zendesk), narzędzia analityczne i widgety A/B. Sprawdź też next.config.js, bo niektóre konfiguracje nagłówków bezpieczeństwa dodają Cache-Control: no-store do tras dynamicznych. Zamień je na private, max-age=0, must-revalidate.
Daniel started in performance work on the SRE side. He spent six years at Spotify on the Web Player team, where he owned the TTI regression budget for the desktop web app and built the internal dashboard that flagged perf regressions per PR before merge. He left in 2023 to join a small consultancy doing performance audits for fintech and travel companies, mostly in the UK and Nigeria.
His subspecialty is server-side rendering tradeoffs: when streaming SSR actually helps, when it makes things worse on flaky 4G, and the real numbers behind React Server Components for content-heavy sites. He's a heavy Playwright user for perf testing, mistrusts most npm dependencies on principle, and is currently writing a small Rust tool to diff WebPageTest waterfalls across deploys. Outside of work he coaches a junior dev meetup in Manchester.
Praktyczny przewodnik po optymalizacji bundle'a JavaScript w 2026: tree shaking, code splitting przez dynamic import, import maps oraz kompresja Brotli. Z gotowymi konfiguracjami Vite 6, Webpacka 5 i bundlewatch dla CI.
LoAF API w Chrome 123+ ujawnia dokładnie ten skrypt, który blokuje główny wątek i pogarsza INP. Zobacz, jak zebrać dane z PerformanceObserver, jak je zinterpretować i jak na ich podstawie obniżyć INP poniżej 200 ms.
Time To First Byte to jeden z najbardziej niedocenianych wskaźników wydajności. W 2026, gdy edge jest standardem a HTTP/3 obsługuje ponad 35% ruchu, klasyczne techniki nie wystarczą. Pokazuję, jak zejść z TTFB poniżej 200 ms — z benchmarkami z produkcji, konfiguracją Cloudflare Workers, HTTP/3 i streaming SSR.