Quick answer: Compress save JSON with pako/gzip (typically 70–90% size reduction). For data >1MB, migrate to IndexedDB.

Players report “Save failed”. Browser console shows QuotaExceededError: Failed to execute 'setItem' on 'Storage'. localStorage’s 5MB quota is full. Verbose JSON saves grow with playtime; eventually the quota fills.

Compress with pako

import pako from 'pako';

function saveCompressed(key, data) {
    const json = JSON.stringify(data);
    const compressed = pako.deflate(json);
    const base64 = btoa(String.fromCharCode.apply(null, compressed));
    try {
        localStorage.setItem(key, base64);
    } catch (e) {
        console.error('Save still too large after compression', e);
    }
}

function loadCompressed(key) {
    const base64 = localStorage.getItem(key);
    if (!base64) return null;
    const compressed = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
    const json = pako.inflate(compressed, { to: 'string' });
    return JSON.parse(json);
}

Typical 100KB JSON compresses to ~15KB. Roughly 6× more saves fit in the quota.

Prune Old Saves

For per-slot save systems, retain only the latest few:

function pruneOldSaves(maxSlots = 3) {
    const keys = Object.keys(localStorage)
        .filter(k => k.startsWith('save_'))
        .sort().slice(0, -maxSlots);
    keys.forEach(k => localStorage.removeItem(k));
}

Periodically called to keep quota usage manageable.

Migrate to IndexedDB

For genuinely large data (>1MB per save), use IndexedDB. Practical wrappers like idb-keyval provide a localStorage-like API:

import { set, get } from 'idb-keyval';

await set('save_main', gameState);
const state = await get('save_main');

IndexedDB allows tens to hundreds of MB depending on browser and user prompts.

Cloud Save Fallback

For ambitious games, cloud-save the actual data; localStorage holds a small lookup ID. The user’s account on your backend stores the full save. No quota issue.

Verifying

Profile save sizes. With compression, target <100KB per save. Run a session that saves frequently; quota usage should stay manageable. Test in Chrome dev tools → Application → Storage to see quota in real time.

“localStorage is small. Compress first, prune second, IndexedDB if you still need more.”

Test on actual deployed origin (not localhost) — some browsers grant more quota to local files than production origins.