Using the fetchLater() API: Reliable Beaconing for the Modern Web
Chrome 135, released in April 2025, introduces a powerful new addition to web developers' toolkit: the fetchLater()
API. This feature solves a long-standing challenge of reliably sending data to servers when a user leaves a page. In this post, we'll explore what the API offers, why it matters, and how to start using it in your applications today.
The Problem with Page Unload Beaconing
Web developers often need to send critical data back to servers when users leave a page. Analytics services, performance metrics, and user interaction tracking all rely on this capability. Traditionally, developers have used a combination of approaches:
- Listening for
beforeunload
,pagehide
, orvisibilitychange
events - Using
navigator.sendBeacon()
orfetch()
with thekeepalive
option
However, these methods face reliability issues, especially on mobile devices where events may not fire consistently. Even with the best practices, developers could only achieve around 82% reliability for unload beaconing.
Enter fetchLater()
The fetchLater()
API provides a cleaner, more reliable solution. It allows you to queue up requests that will be sent either:
- When the page is unloaded (tab closed, navigation away, etc.)
- After a specified timeout
- When the browser decides it's time to send it
Basic Usage
The API syntax is designed to feel familiar to developers who use fetch()
:
// Basic usage - will send when page unloads
const result = fetchLater('/analytics-endpoint', {
method: 'POST',
body: JSON.stringify({ sessionData: collectSessionData() })
});
// Check if the request has been sent
console.log(result.activated); // false initially, true after sending
Adding a Timeout
You can specify an activateAfter
option (in milliseconds) to ensure the request is sent after a certain time, even if the page hasn't been unloaded:
// Send after 1 minute OR when page unloads (whichever comes first)
const oneMinute = 60 * 1000;
fetchLater('/analytics-endpoint', {
method: 'POST',
body: JSON.stringify({ sessionData: collectSessionData() }),
activateAfter: oneMinute
});
Updating Data Before Sending
One of the most powerful features is the ability to update the queued request before it's sent. This is perfect for analytics that accumulate data over time:
const analyticsData = {
pageViews: 1,
interactions: []
};
let controller = new AbortController();
let fetchLaterResult;
function queueAnalyticsBeacon() {
// Abort any existing request
controller.abort();
controller = new AbortController();
// Queue a new request with the latest data
fetchLaterResult = fetchLater('/analytics-endpoint', {
method: 'POST',
body: JSON.stringify(analyticsData),
signal: controller.signal,
activateAfter: 60 * 60 * 1000 // 1 hour
});
}
// Initial queue
queueAnalyticsBeacon();
// Update when user interacts
function recordInteraction(interactionType) {
analyticsData.interactions.push({
type: interactionType,
timestamp: Date.now()
});
// Update the queued beacon
queueAnalyticsBeacon();
}
Real-World Example: Web Vitals Reporting
Here's how you might use fetchLater()
to send Core Web Vitals data:
import {onCLS, onINP, onLCP} from 'web-vitals';
const metrics = new Set();
let controller;
let fetchLaterResult;
function updateMetrics(metric) {
// Clear already-sent metrics if request was activated
if (fetchLaterResult?.activated) {
metrics.clear();
}
// Add the new/updated metric
metrics.add(metric);
// Prepare the data payload
const body = JSON.stringify(Array.from(metrics));
// Abort any existing fetchLater() and queue a new one
controller?.abort();
controller = new AbortController();
fetchLaterResult = fetchLater('/vitals-analytics', {
method: 'POST',
body,
signal: controller.signal,
activateAfter: 60 * 60 * 1000 // 1 hour timeout for regular sending
});
}
// Register the web vitals callbacks
onCLS(updateMetrics);
onINP(updateMetrics);
onLCP(updateMetrics);
Error Handling
It's good practice to handle potential errors, especially quota limitations:
try {
fetchLater('/analytics-endpoint', {
method: 'POST',
body: JSON.stringify(analyticsData)
});
} catch (e) {
if (e instanceof QuotaExceededError) {
// Handle quota exceeded
console.warn('Analytics beacon quota exceeded');
} else {
// Handle other errors
console.error('Error queueing analytics beacon:', e);
}
}
Understanding Quotas and Limitations
To prevent abuse, browsers enforce limits on fetchLater()
usage:
- Total bandwidth for a top-level document is capped at 640KB
- Each reporting origin is limited to 64KB
- All pending requests are flushed when a document enters the browser's back/forward cache
Browser Support
As of May 2025:
- Chrome: Supported since version 135 (April 2025)
- Firefox: Under consideration
- Safari: Not yet implemented
For browsers that don't support fetchLater()
, you can implement a fallback using the traditional approach:
function sendAnalytics(data) {
if (typeof window.fetchLater === 'function') {
try {
fetchLater('/analytics', {
method: 'POST',
body: JSON.stringify(data)
});
return true;
} catch (e) {
console.warn('fetchLater failed, falling back to sendBeacon', e);
}
}
// Fallback to sendBeacon
return navigator.sendBeacon('/analytics', JSON.stringify(data));
}
// Register traditional unload handlers as fallback
window.addEventListener('pagehide', () => {
sendAnalytics(analyticsData);
});
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
sendAnalytics(analyticsData);
}
});
Demo
I have built a simple Demo page for the fetchLater API here: https://www.undefinednull.com/fetch-later-demo/
Conclusion
The fetchLater()
API represents a significant improvement for web developers who need reliable beaconing. It simplifies code, improves reliability, and provides a more declarative approach to handling page unload data transmission.
By adopting this API (with appropriate fallbacks), developers can ensure their analytics, metrics, and other critical data have the best chance of making it back to servers, regardless of how users exit their web applications.
For more details, check the official documentation and consider experimenting with the API in your applications today.