TLDR -> For developers looking to directly jump to the solution
➡️ click here
Imagine you're building a simple To-Do List app. Your app has two pages:
- To-Do List Page (/todos) – Displays all tasks and has a "Create Task" button.
- Task Details Page (/todos/:id) – Shows details of a specific task.
The Problem
A user creates a new task on the To-Do List page. ✅ It appears in the list.
The user clicks the new task, goes to its details page, and updates something.
The user clicks the browser back button to return to the To-Do List.
💥 The newly created task is missing from the list! But when they manually refresh, it reappears.
At first, I thought it was a react state update issue and tried to debug in that direction. However, on deeper investigation, I noticed that the API calls were made as expected on the initial render, but were getting old data as the response. This was weird because the API was working perfectly fine in every other case except for this backwards-navigation case.
After searching for this issue I found out there's something called as Back/Forward Cache (bfcache) in modern browsers which is an optimization technique used to reduce load times. However, in pages that require updated data, this can prove to be an issue.
Here’s a technical breakdown of how it works:
1. Freezing the Page (Page Lifecycle Management)
- When a user leaves a page (e.g., clicking a link or using the back button), the browser pauses all JavaScript execution and freezes the page state.
- Instead of destroying the page (like a normal navigation would), it is kept in memory, including the DOM, JavaScript heap, and event listeners.
- The page is put into a "suspended" state where it stops running scripts but keeps all its data intact.
2. Storing in bfcache
- Unlike normal caching (which only stores fetched resources like images, CSS, and HTML), bfcache stores the entire page, including JavaScript memory.
- This means when you return, there’s no need to re-run scripts, make API calls, or reload assets.
3. Restoring the Page (pageshow Event)
- When the user navigates back or forward, the browser instantly restores the frozen page, unfreezing JavaScript execution from where it left off.
- The pageshow event fires, with
event.persisted === true
, indicating that the page was loaded from bfcache instead of reloading from the server.
4. When Does bfcache Not Work?
bfcache won't work if:
- The page has unload event listeners (old websites used this).
- Certain security restrictions apply (e.g., authentication headers, no-store cache).
- The page uses window.opener or specific JavaScript APIs like IndexedDB in ways that prevent caching.
The Solution
When the page is loaded from the BFCache, the onload
event won't be triggered. So, in order to determine if the page was restored from the bfcache, we must instead listen to the pageshow
event and examine the event.persisted
attribute. This can be useful for determining whether data is outdated or not. If it is restored from bfcache, it will be stale so then update it accordingly using an API re-fetch or update function.
You can use the following event listener to see whether or not the user came from the BFCache, like:
window.addEventListener('pageshow', function(event) {
if (event.persisted) {
// The page was restored from the bfcache
// Re-fetch data
}
});
Alternate solution
Adding headers to your API call like the "Cache-Control": "no-store" and "Pragma": "no-cache" headers ensure that the browser and intermediate caches do not store any response, forcing a fresh API call on every request.
Best for:
- Real-time data that must always be up-to-date.
- Financial transactions, live dashboards, or frequently changing content.
Avoid for:
- Static or rarely updated data (e.g., images, documents, or user profiles).
- Situations where reducing server load and improving performance is a priority.
const response = await fetch(
`api-url-call`,
{
headers: {
"Cache-Control": "no-store",
Pragma: "no-cache"
},
cache: "no-store"
}
);
Essential vs. Optional
✅ Cache-Control: "no-store" → Most important
- Ensures the browser and intermediate caches do not store the response at all.
- Works in modern browsers and is the recommended approach.
⚠️ Pragma: "no-cache" → Optional
- A legacy header from HTTP/1.0; mainly useful for compatibility with older systems.
- Can be omitted if you're dealing only with modern browsers.
✅ cache: "no-store" (fetch option) → Recommended for fetch calls
- Ensures fetch() bypasses the cache entirely.
- This is only for fetch requests and does not affect browser cache behavior for other requests.
Why Doesn't bfcache Work in Local but Works in Staging/Production?
Localhost Behaves Differently 🏠
Some browsers disable bfcache by default for localhost or treat it differently to avoid issues during development.
In local, navigation may always trigger a full page reload instead of restoring from memory.
Hot Reloading & Code Changes 🔄
In local, tools like React Fast Refresh or Webpack invalidate page states when code updates.
Even without refreshing, this can prevent bfcache from storing the page.
Missing Production Headers & Settings ⚙️
Staging/production environments often have caching headers that enable bfcache.
Local development servers might lack these, preventing pages from being stored in memory.
Service Workers & API Behavior 🌐
In production, service workers or caching strategies might allow bfcache to work properly.
Locally, APIs might behave differently (e.g., not using caching rules), leading to different navigation behavior.
Top comments (0)