The 5MB Ceiling: How 'Discovery Mode' Can Crash Your Web App

A warning for cookie consent trackers and listeners

In modern web development, we often treat localStorage as a bottomless bucket for small bits of state—session tokens, UI preferences, or cart data. But as I recently discovered while scaling our international DTC infrastructure, that bucket has a very hard, very unforgiving ceiling.

The Symptom: QuotaExceededError

During a routine audit of our tracking stack, I hit the dreaded QuotaExceededError. This error occurs when a website attempts to save more data than the browser allows for that specific domain. Most browsers (Chrome, Safari, Edge) cap this at roughly 5MB to 10MB.

While investigating, I found the culprit wasn't our own code, but Osano—our consent management platform—which was running in Discovery Mode.

The Learning: When Auditing Becomes Bloating

In "Discovery Mode," compliance tools like Osano act as a "tattle-tale." Every time a new script, cookie, or network request is detected, it logs it to a local storage key (in this case, osano_consentmanager_tattles) to build an audit list.

For a complex site with multiple integrations—like our Klaviyo migration, Rollbar error tracking, and Shopify BI connectors—this list grows exponentially. In our case, this single "audit" key ballooned to over 9MB, effectively hijacking the entire storage quota and blocking every other critical script from functioning.

The Diagnostic Tool

To find the "smoking gun," I used a script to visualize exactly how our 5MB-10MB was being spent. If you're seeing storage errors, run this in your console to see a ranked list of your data-hogs:

/**
 * LocalStorage Usage Audit
 * Calculates size in KB (assuming UTF-16) and sorts by size.
 */
(function() {
  const data = [];
  let totalSize = 0;

  for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i);
    const value = localStorage.getItem(key);
    // localStorage uses UTF-16, so each character is 2 bytes
    const sizeInBytes = (key.length + value.length) * 2;
    const sizeInKB = sizeInBytes / 1024;

    data.push({
      "Item Name": key,
      "Size (KB)": parseFloat(sizeInKB.toFixed(2))
    });
    totalSize += sizeInKB;
  }

  // Sort descending by size to find the culprit
  data.sort((a, b) => b["Size (KB)"] - a["Size (KB)"]);

  console.table(data);
  console.log(`Total Usage: ${(totalSize / 1024).toFixed(2)} MB / 10.00 MB`);
  
  if (totalSize > 4500) {
    console.warn("CRITICAL: You are nearing the browser-imposed 5MB-10MB limit.");
  }
})();

Limits by browser

Browser Local Storage Limit Total Storage (IndexedDB/Cache) Notes
Chrome ~5MB - 10MB Up to 80% of disk space Shared pool approach; mirrors Edge.
Firefox ~5MB - 10MB Up to 50% of free disk space Generous with IndexedDB; prompts for large data.
Safari ~5MB Varies based on disk space Most restrictive; aggressive with data purging.
Edge ~5MB - 10MB Up to 80% of disk space Built on Chromium; same limits as Chrome.

The Three Keys to Better Storage Management

  1. Avoid Discovery Mode in Production: Tools meant for auditing should be used in staging. Once you have your baseline of trackers and cookies, move to "Managed" or "Permissive" modes to stop the storage bloat.
  2. Monitor Your "UTF-16 Tax": Remember that localStorage stores strings in UTF-16 format. This means 1MB of raw text actually takes up 2MB of your quota.
  3. Fail Gracefully: Always wrap your localStorage.setItem() calls in a try...catch block. If the storage is full, your app should continue to work, even if it means losing non-essential cached data.

Final Thoughts

As we continue to modernize our core technical foundation, it’s a reminder that even "passive" compliance tools have a footprint. Keeping a lean localStorage isn't just about clean code—it's about ensuring your site has the "breathing room" it needs to scale globally.