DEV Community

reactuse.com
reactuse.com

Posted on

E-commerce Feeds Caching Strategy: Temu vs PDD's Technical Choices

πŸš€ 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
Enter fullscreen mode Exit fullscreen mode

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.

Amazon Example


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');
    }
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode
// 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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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();
};
Enter fullscreen mode Exit fullscreen mode

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);
};
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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

  1. User First: Temu targets high-end international users, choosing ultimate performance bfcache; PDD targets domestic lower-tier markets, choosing better compatibility custom solutions

  2. Context-appropriate: Different market environments, device distributions, and network conditions determine different technical paths

  3. 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)