π Discover 100+ powerful React Hooks possibilities! Visit www.reactuse.com for comprehensive documentation with MCP (Model Context Protocol) support, or install via npm install @reactuses/core to supercharge your React development efficiency with our extensive hook collection
Problem Scenario: Pain Points of Feeds Flow
In modern web applications, information feeds are one of the most common interaction patterns. Users typically follow this behavior path:
Feeds Homepage β Click Article β Read Details β Return to Continue Browsing
However, traditional MPA (Multi-Page Application) architecture presents obvious user experience issues in this scenario:
Core Pain Points
- Reload Required: Returning requires re-requesting data, causing wait times
- Position Loss: User scroll position cannot be maintained, requiring users to relocate their previous reading position
- Performance Overhead: Unnecessary network requests and page rendering increase server pressure
- Experience Fragmentation: Loading processes interrupt users' browsing fluidity
Real Impact
In e-commerce, news, and social applications primarily focused on information browsing, these issues are particularly prominent. Users may need to:
- Re-scroll to their previous position
- Wait for already-viewed content to reload
- Endure unnecessary white screen time
Real-World Case Comparison
In actual e-commerce applications, we can observe interesting differences in technical choices:
- Temu (International Market): Primarily relies on browser-native bfcache
- PDD (Domestic Market): Uses custom caching strategies
This difference is not coincidental but based on deep consideration of target user groups and technical environments.
Deep Analysis of Two Mainstream Solutions
Based on e-commerce giants' actual choices, we focus on analyzing two mainstream technical solutions: browser-native capabilities and custom storage solutions.
Solution 1: bfcache (Temu's Choice)
Working Principle
bfcache (Back/Forward Cache) is a browser-native optimization technology that can save complete page state (including DOM, JavaScript state, scroll position) in memory.
// Detect bfcache restoration
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
console.log('Page restored from bfcache');
// Optional: refresh time-sensitive data
refreshTimelyData();
}
});
// Handle page leaving
window.addEventListener('pagehide', (event) => {
if (!event.persisted) {
console.log('Page truly unloaded, will not enter bfcache');
}
});
Advantages
- Zero Configuration: Modern browsers support automatically, no additional code needed
- Ultimate Performance: Millisecond-level recovery speed, faster than any custom solution
- Complete State: Automatically maintains scroll position, form state, DOM state
- Memory Optimization: Browser intelligently manages memory, automatically clears expired cache
Limitations
// The following situations will prevent bfcache:
// 1. Registered beforeunload/unload events
window.addEventListener('beforeunload', handler); // β Will prevent
// 2. Active network connections
const ws = new WebSocket('ws://example.com'); // β Will prevent
// 3. Ongoing network requests
fetch('/api/data'); // β If still ongoing during page switch
// 4. Using certain APIs
navigator.sendBeacon(); // β Will prevent
Why Temu Chooses bfcache
International Market Characteristics
- Better Device Performance: International users generally have higher-spec devices with sufficient memory
- Excellent Network Environment: Good 4G/5G network coverage, high WiFi penetration
- Modern Browser Versions: Chrome, Safari and other modern browsers dominate
- User Habits: Accustomed to using browser back button for navigation
E-commerce Scenario Match
- Product Browsing Mode: Users frequently switch between lists and details
- Performance Priority: Ultimate return speed is core competitiveness
- Development Efficiency: Zero-configuration solution saves development and maintenance costs
Solution 2: sessionStorage (PDD's Choice)
Working Principle
Use sessionStorage to store page state on the client side and restore it when returning. This is a simple and effective fallback solution.
// Cache page state
function cacheCurrentPage() {
const html = document.documentElement.outerHTML;
const feedsData = this.feeds;
const cacheData = {
html: html,
data: feedsData,
timestamp: Date.now(),
url: window.location.href
};
// Store to sessionStorage
sessionStorage.setItem('feeds-cache-html', cacheData.html);
sessionStorage.setItem('feeds-cache-data', JSON.stringify(cacheData.data));
console.log('Page state cached to sessionStorage');
}
// Check and restore cache on page load
function checkAndRestoreFromCache() {
const cachedHtml = sessionStorage.getItem('feeds-cache-html');
const cachedData = sessionStorage.getItem('feeds-cache-data');
if (cachedHtml && cachedData) {
console.log('Restoring page state from sessionStorage');
// Immediately clear cache to ensure single use
sessionStorage.removeItem('feeds-cache-html');
sessionStorage.removeItem('feeds-cache-data');
// Restore page content
document.documentElement.innerHTML = cachedHtml;
// Restore data state
window.cachedData = JSON.parse(cachedData);
window.isRestoringFromCache = true;
return true;
}
return false;
}
// Page initialization logic
class FeedsPage {
async init() {
// Check if restoring from cache
if (window.isRestoringFromCache && window.cachedData) {
console.log('Initializing page with cached data');
this.feeds = window.cachedData;
this.renderFeeds();
this.showCacheNotice();
// β οΈ Critical step: Rebind event listeners
// HTML structure is restored, but event listeners are lost, need to rebind
this.rebindEvents();
} else {
console.log('First load, fetching data from network');
await this.loadFeeds();
// Normal event binding for first load
this.bindEvents();
}
}
// Rebind all event listeners
rebindEvents() {
console.log('Rebinding event listeners...');
// 1. Rebind click events for feeds list items
document.querySelectorAll('.feed-item').forEach(item => {
item.addEventListener('click', this.handleFeedClick.bind(this));
});
// 2. Rebind events for action buttons
document.querySelectorAll('.like-btn').forEach(btn => {
btn.addEventListener('click', this.handleLike.bind(this));
});
document.querySelectorAll('.share-btn').forEach(btn => {
btn.addEventListener('click', this.handleShare.bind(this));
});
// 3. Rebind scroll events (infinite loading)
window.addEventListener('scroll', this.handleScroll.bind(this));
// 4. Rebind page leave events
window.addEventListener('beforeunload', this.handleBeforeUnload.bind(this));
console.log('Event listeners rebinding completed');
}
// Normal event binding method
bindEvents() {
// Same logic as rebindEvents, but may include more initialization code
this.rebindEvents();
}
}
Advantages
- Simple Implementation: Less code, easy to understand and maintain
- Good Compatibility: All modern browsers support sessionStorage
- Lightweight: No additional tech stack needed
- Debug Friendly: Can directly view cache content in developer tools
Core Challenge: Event Listener Reconstruction
The biggest challenge of sessionStorage restoration is event listener loss:
// The problem: Only DOM structure is restored, event listeners are lost
document.documentElement.innerHTML = cachedHtml; // β Only structure, no events
Solution in React Applications:
// Cache restoration implementation in React
class FeedsApp extends React.Component {
componentDidMount() {
const cachedHtml = sessionStorage.getItem('feeds-cache-html');
const cachedData = sessionStorage.getItem('feeds-cache-data');
if (cachedHtml && cachedData) {
// 1. Clear cache
sessionStorage.removeItem('feeds-cache-html');
sessionStorage.removeItem('feeds-cache-data');
// 2. Directly set DOM (quick content display)
document.querySelector('#root').innerHTML = cachedHtml;
// 3. Critical step: Recreate React root node, restore interactivity
const feedsData = JSON.parse(cachedData);
// Use React 18's createRoot API
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<FeedsPage initialData={feedsData} fromCache={true} />);
console.log('React component remounted, event listeners restored');
} else {
// Normal first load
this.loadInitialData();
}
}
// Cache current state when leaving page
handleBeforeUnload = () => {
const html = document.querySelector('#root').innerHTML;
const data = this.state.feeds;
sessionStorage.setItem('feeds-cache-html', html);
sessionStorage.setItem('feeds-cache-data', JSON.stringify(data));
}
}
// Feeds page component
function FeedsPage({ initialData, fromCache }) {
const [feeds, setFeeds] = useState(initialData || []);
useEffect(() => {
if (fromCache) {
console.log('Restored from cache, skipping data loading');
// Optional: background data refresh
refreshDataInBackground();
} else {
// First load fetch data
loadFeedsData().then(setFeeds);
}
}, [fromCache]);
// Event handlers will be automatically rebound
const handleFeedClick = useCallback((feedId) => {
console.log('Clicked Feed:', feedId);
// React's event system handles automatically
}, []);
return (
<div className="feeds-container">
{feeds.map(feed => (
<div
key={feed.id}
className="feed-item"
onClick={() => handleFeedClick(feed.id)} // β
Events automatically restored
>
{feed.title}
</div>
))}
</div>
);
}
Other Limitations
// Other limitations of sessionStorage:
// 1. Storage size limit (usually 5-10MB)
// 2. Only valid for current session
// 3. Cannot share across tabs
// 4. Need manual cache lifecycle management
// 5. Event listeners need rebinding (key challenge)
Why PDD Chooses sessionStorage
Domestic Market Characteristics
- Varied Device Performance: Many low-end Android devices with limited memory
- Complex Network Environment: 2G/3G networks still exist, unstable speeds
- Browser Fragmentation: Various WebView kernels, high compatibility requirements
- Large User Base: Lower-tier market users are more sensitive to performance
Technical Considerations
- Compatibility Priority: sessionStorage is nearly universally compatible, no compatibility risks
- Strong Controllability: Complete control over caching logic, flexible adjustment based on business needs
- Memory Friendly: Doesn't rely on browser memory cache, reduces pressure on low-end devices
- Simple Debugging: Easier problem troubleshooting and performance optimization
Business Adaptation
- Fine-grained Control: Can dynamically adjust caching strategies based on user network conditions
- Data Analytics: Convenient for collecting user behavior data and performance metrics
- A/B Testing: Flexible testing of different caching strategies
In-depth Comparison of Both Solutions
Dimension | bfcache (Temu) | sessionStorage (PDD) |
---|---|---|
Performance | βββββ Millisecond recovery | βββ Requires re-rendering |
Compatibility | ββββ Modern browsers | βββββ Universal compatibility |
Memory Usage | ββ Uses browser memory | βββββ Almost no memory usage |
Development Complexity | βββββ Zero config | ββ Need to handle event reconstruction |
Event Listeners | βββββ Automatically maintained | ββ Need rebinding |
State Preservation | βββββ Complete preservation | βββ Data state only |
Controllability | β Browser controlled | βββββ Fully controllable |
Debugging Difficulty | βββββ Simple | βββ Need to debug event binding |
Low-end Devices | ββ May lack memory | βββββ Stable performance |
Data Collection | β Hard to monitor | βββββ Easy to track |
Selection Strategy: Context-appropriate Technical Decisions
International Market β Choose bfcache
// Temu's technical selection logic
const shouldUseBfcache = (userAgent, market) => {
return market === 'overseas' &&
isModernBrowser(userAgent) &&
hasEnoughMemory();
};
Applicable Conditions:
- Target users have good device performance
- Stable network environment
- Modern browsers dominate
- Pursuing ultimate performance experience
Domestic Market β Choose sessionStorage
// PDD's technical selection logic
const shouldUseSessionStorage = (userAgent, market) => {
return market === 'domestic' ||
isLowEndDevice(userAgent) ||
isOldBrowser(userAgent);
};
Applicable Conditions:
- Varied device performance
- Need to support many low-end devices
- Complex browser environment
- Need fine-grained control and data collection
Best Practice: Progressive Enhancement Strategy
In actual projects, you can combine both solutions for optimal results:
// Smart cache strategy selection
class SmartCacheStrategy {
constructor() {
this.strategy = this.detectOptimalStrategy();
}
detectOptimalStrategy() {
// 1. Detect device performance
const deviceMemory = navigator.deviceMemory || 2;
const isLowEndDevice = deviceMemory <= 2;
// 2. Detect network environment
const connection = navigator.connection;
const isSlowNetwork = connection?.effectiveType === 'slow-2g' ||
connection?.effectiveType === '2g';
// 3. Detect browser support
const supportsBfcache = 'onpageshow' in window;
// 4. Intelligently select strategy
if (isLowEndDevice || isSlowNetwork || !supportsBfcache) {
return 'sessionStorage';
} else {
return 'bfcache';
}
}
init() {
if (this.strategy === 'bfcache') {
this.initBfcacheStrategy();
} else {
this.initSessionStorageStrategy();
}
}
initSessionStorageStrategy() {
const cachedHtml = sessionStorage.getItem('feeds-cache-html');
const cachedData = sessionStorage.getItem('feeds-cache-data');
if (cachedHtml && cachedData) {
// Restore HTML structure
document.body.innerHTML = cachedHtml;
// β οΈ Critical: Rebind all event listeners
this.rebindAllEvents();
// Restore data state
this.restoreDataState(JSON.parse(cachedData));
console.log('Restored from sessionStorage, events rebound');
} else {
this.normalInit();
}
}
rebindAllEvents() {
// Rebind all interactive events
this.bindFeedsEvents();
this.bindScrollEvents();
this.bindNavigationEvents();
this.bindFormEvents();
}
}
Conclusion: Business Wisdom Behind Technical Choices
Through analyzing Temu and PDD's technical choices, we can see: Technical solution selection is often not purely a technical issue, but a comprehensive reflection of business strategy and user demographics.
Core Insights
User First: Temu targets high-end international users, choosing ultimate performance bfcache; PDD targets domestic lower-tier markets, choosing better compatibility custom solutions
Context-appropriate: Different market environments, device distributions, and network conditions determine different technical paths
Pragmatism: There's no best technology, only the most suitable technology. Complex solutions aren't necessarily better than simple ones
Practical Recommendations
In your next project, consider:
- Who are your users? What's their device performance like?
- What's your application scenario? Are you pursuing ultimate performance or stable compatibility?
- What's your technical team like? Do you prefer using new technologies or mature solutions?
bfcache represents the ultimate in browser-native capabilities, sessionStorage embodies the pragmatism of engineering solutions. At the crossroads of technical choices, letting business needs and user value guide the direction is often more important than the advancement of technology itself.
Top comments (0)