The other day, I was chatting with a friend about retrieving request data for a script outside the main project without re-triggering a fetch. We hit a wall: how do we do this cleanly?
After some digging, I stumbled upon Cache.match() on MDN. It was exactly what we needed. It reminded me of something we all face: the "comfort zone" trap.
We often code by reflex or to save time (which isn't a bad thing), but we forget that browsers are evolving fast. Here is a selection of native APIs that are worth your attention in 2026.
📦 Replacing Dependencies
1. Intl.RelativeTimeFormat
Replaces: dayjs, moment.js
MDN Documentation
This API turns raw data into human-readable phrases.
const rtf = new Intl.RelativeTimeFormat('en', {
numeric: 'auto' // 'auto' enables phrases like "yesterday" instead of "1 day ago"
});
function formatRelative(date) {
const now = new Date();
const diffInMs = date - now;
// Convert milliseconds to days
const diffInDays = Math.round(diffInMs / (1000 * 60 * 60 * 24));
return rtf.format(diffInDays, 'day');
}
// Tests
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const longAgo = new Date();
longAgo.setDate(longAgo.getDate() - 5);
console.log(formatRelative(yesterday)); // "yesterday" (thanks to numeric: 'auto')
console.log(formatRelative(longAgo)); // "5 days ago"
Note: it doesn’t calculate the units for you (yet—wait for the Temporal API to be fully stable). You need to tell it if you’re dealing with minutes, hours, etc.
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
// Define thresholds in seconds
const UNITS = [
{ unit: 'month', seconds: 2592000 },
{ unit: 'day', seconds: 86400 },
{ unit: 'hour', seconds: 3600 },
{ unit: 'minute', seconds: 60 },
{ unit: 'second', seconds: 1 }
];
function formatAutoRelative(date) {
const diffInSeconds = Math.round((date - new Date()) / 1000);
// Find the unit corresponding to the first threshold reached
for (const { unit, seconds } of UNITS) {
if (Math.abs(diffInSeconds) >= seconds || unit === 'second') {
const value = Math.round(diffInSeconds / seconds);
return rtf.format(value, unit);
}
}
}
// --- Tests ---
console.log(formatAutoRelative(new Date(Date.now() - 5000))); // "5 seconds ago"
console.log(formatAutoRelative(new Date(Date.now() - 3600000))); // "1 hour ago"
console.log(formatAutoRelative(new Date(Date.now() + 86400000))); // "tomorrow"
đź’ˇ Pro-tip: Couple it with
Intl.DateTimeFormatfor a "fallback" strategy. If the delay exceeds 7 days, switch from relative time to a full date.
2. structuredClone()
Replaces: lodash.cloneDeep, JSON.parse(JSON.stringify(obj))
MDN Documentation
The JSON method is what we call "lossy cloning." It works for simple objects but destroys anything it doesn't understand (Dates, Maps, Sets, RegEx).
const original = {
date: new Date(),
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
regex: /hello/g,
undefinedVal: undefined
};
// ❌ BEFORE (Hack JSON)
const fakeClone = JSON.parse(JSON.stringify(original));
console.log(fakeClone.date); // "2026-02-10T..." (Converted to a STRING, not a Date object anymore!)
console.log(fakeClone.map); // {} (Empty, Maps are lost)
console.log(fakeClone.regex); // {} (Lost)
console.log(fakeClone.undefinedVal); // Gone (the key no longer exists)
const original = {
date: new Date(),
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
regex: /hello/g
};
// âś… NOW (2026 Standard)
const realClone = structuredClone(original);
console.log(realClone.date instanceof Date); // true
console.log(realClone.map.get('key')); // "value"
console.log(realClone.regex.test('hello')); // true
Note: It won’t clone functions or DOM elements, as they are bound to their execution context.
⚡ Mastering Data Flow
3. AbortController
MDN Documentation
Think of this as the "Emergency Stop" button for your code. If a user frantically clicks a "Category" filter, without AbortController, you fire X fetch requests. Even if you only display the last one, the previous ones still consume bandwidth and CPU in the background.
let currentController;
const fetchData = async () => {
// 1. Cancel the previous request if it exists
if (currentController) currentController.abort();
// 2. Create a new signal for the current request
currentController = new AbortController();
try {
const res = await fetch('/api/data', { signal: currentController.signal });
return await res.json();
} catch (err) {
if (err.name === 'AbortError') return; // Silent, this is an intentional cancellation
throw err;
}
};
Note: It's versatile! You can use it with Event Listeners or setTimeout to clean up side effects.
const controller = new AbortController(); // Attach the signal to multiple events
window.addEventListener('resize', () => console.log('Resized'), { signal: controller.signal });
window.addEventListener('scroll', () => console.log('Scrolled'), { signal: controller.signal });
// To delete everything at once: controller.abort();
4. BroadcastChannel
MDN Documentation
Allows different navigation contexts (tabs, windows, iframes) from the same origin to communicate in real-time without a server or complex localStorage hacks. Perfect for syncing a shopping cart or handling a global logout.
// --- Shared Logic ---
const cartChannel = new BroadcastChannel('shop_cart_sync');
// --- TAB A (The "Emitter") ---
function addToCart(product) {
// 1. Business Logic: save to localStorage for persistence
const cart = JSON.parse(localStorage.getItem('cart') || '[]');
cart.push(product);
localStorage.setItem('cart', JSON.stringify(cart));
// 2. Update the UI of the current tab
updateCartUI(cart.length);
// 3. Notify all other tabs instantly
cartChannel.postMessage({
type: 'CART_UPDATED',
count: cart.length,
lastAdded: product.name
});
}
// --- TAB B, C, D (The "Listeners") ---
cartChannel.onmessage = (event) => {
if (event.data.type === 'CART_UPDATED') {
// Update the cart counter in the header
updateCartUI(event.data.count);
// Bonus: Show a little toast notification
console.log(`An item (${event.data.lastAdded}) was added from another tab!`);
}
};
đź’ˇ Pro-tip: In a SPA, always remember to close the channel when the component is unmounted:
bc.close();.
5. Navigator.sendBeacon()
MDN Documentation
How do you send data to the server just before a user leaves the page? Standard fetch often fails because the browser kills the process before the request finishes. sendBeacon() is asynchronous and guaranteed to finish in the background.
// Prepare analytics data
const analyticsData = {
articleId: '123',
timeSpent: 450,
completion: 0.85
};
// Use visibilitychange event (more reliable than 'unload' in 2026)
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
// Convert data to Blob or FormData
const blob = new Blob([JSON.stringify(analyticsData)], { type: 'application/json' });
// The "fire and forget" magic
navigator.sendBeacon('/api/analytics', blob);
}
});
🎨 Performance & Native UI
6. Intersection Observer API
MDN Documentation
The ultimate tool for lazy-loading and scroll-based animations without killing your performance.
<img data-src="high-res-photo.jpg" src="placeholder.jpg" class="lazy-load" alt="Description">
// 1. Configuration: trigger when 10% of the element is visible
const options = {
root: null, // use browser viewport
threshold: 0.1
};
// 2. Observer creation
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
// If the element is within the viewport
if (entry.isIntersecting) {
const img = entry.target;
// Replace placeholder with the actual image
img.src = img.dataset.src;
img.classList.add('fade-in'); // Adding a subtle, optional animation.
// Once loaded, stop observing this image (performance gain)
observer.unobserve(img);
}
});
}, options);
// 3. Start observing all "lazy" images
document.querySelectorAll('.lazy-load').forEach(img => {
observer.observe(img);
});
7. Cache.match()
MDN Documentation
Need to share API data between two independent scripts without a global variable or a second network call? This is exactly how I solved my problem the other day.
⚠️ Important Note: Don't confuse this with standard HTTP caching. While the browser manages the "HTTP Cache" automatically via headers, the Cache API is entirely programmable. You are the one deciding exactly what to store, update, or delete.
// Fetch and cache
async function fetchAndCacheConfig() {
const cache = await caches.open('app-resources');
const url = '/api/user-config';
const response = await fetch(url);
// We must clone the response because a response body can only be read once
await cache.put(url, response.clone());
return response.json();
}
// No fetch, check if it is in cache
async function getExistingData() {
const cache = await caches.open('app-resources');
// Check if a request matching this URL exists in the cache
const cachedResponse = await cache.match('/api/user-config');
if (cachedResponse) {
const data = await cachedResponse.json();
console.log("Data retrieved from cache with no new network call:", data);
return data;
}
console.log("Cache miss: no data found.");
}
Note: Like most modern web APIs, this is only available in HTTPS contexts (and localhost)
8. DocumentFragment (Old but gold)
Specific to Vanilla JS or Web Components
MDN Documentation
A lightweight, "off-screen" DOM container. Use it to batch DOM injections and avoid multiple expensive reflows.
const list = document.querySelector('#ul-list');
const fragment = document.createDocumentFragment();
['Apple', 'Pear', 'Banana'].forEach(fruit => {
const li = document.createElement('li');
li.textContent = fruit;
fragment.appendChild(li); // No rendering here yet
});
list.appendChild(fragment); // A single reflow for all 3 elements!
🛠️ Dev Comfort & Debugging
9. console.table()
MDN Documentation
Stop squinting at messy object logs. Use console.table(data) for a clean, sortable grid in your devtools.
10. URLSearchParams
MDN Documentation
Stop using RegEx to parse URLs.
new URLSearchParams(window.location.search).get('id') is all you need.
đź§Ş The Experimental One
11. EyeDropper API
MDN Documentation
Adding this one for the 'cool factor'. A native color picker that can grab colors from anywhere on the user's screen—even outside the browser.
async function pickColor() {
if (!window.EyeDropper) return console.log("Not supported");
const dropper = new EyeDropper();
try {
const result = await dropper.open(); // Opens the system color picker (magnifying glass)
console.log(result.sRGBHex); // Ex: #ff0000
} catch (e) {
console.log("Cancel");
}
}
Conclusion
The browser's Web API list is massive. I encourage you to browse it regularly.
Which one is your favorite? Do you have any other native 'hidden gems'?
Top comments (2)
Nice list! Many of these can provide significant performance gains. For real-world apps, I’ve found AbortController + request dedupe (cancel in-flight on rapid UI changes) and IntersectionObserver (lazy-load) tend to improve things a lot.
One extra tip: when you use the Cache API, be explicit about invalidation (version keys / max-age strategy), so you don’t ship “forever stale” config.
Thanks! And you're absolutely right about invalidation to avoid getting stuck with stale content, thanks for the tip!