DEV Community

Quentin Merle
Quentin Merle

Posted on • Edited on

Javascript in 2026: 11 Under-the-Radar Browser APIs

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

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

đź’ˇ Pro-tip: Couple it with Intl.DateTimeFormat for 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)
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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

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

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

đź’ˇ 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);
  }
});
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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)

Collapse
 
apogeewatcher profile image
Apogee Watcher

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.

Collapse
 
quentin_merle profile image
Quentin Merle

Thanks! And you're absolutely right about invalidation to avoid getting stuck with stale content, thanks for the tip!