Resumen de cinco mecanismos de almacenamiento del navegador
Los navegadores modernos ofrecen 5 mecanismos de almacenamiento principales, cada uno diseñado para diferentes objetivos y casos de uso:
| Almacenamiento |
Tipo de datos |
Límite de capacidad |
Persistencia |
Acceso por hilo |
| localStorage |
Clave-valor de cadena |
~5-10 MB |
Permanente |
Solo hilo principal |
| sessionStorage |
Clave-valor de cadena |
~5-10 MB |
Al cerrar pestaña |
Solo hilo principal |
| IndexedDB |
Datos estructurados + Blob |
Cientos de MB~ilimitado |
Permanente |
Hilo principal + Worker |
| Cache API |
Pares Request/Response |
Compartido con cuota de IndexedDB |
Permanente |
Hilo principal + Worker |
| OPFS |
Sistema de archivos |
Compartido con cuota de IndexedDB |
Permanente |
Solo Worker (síncrono) |
localStorage y sessionStorage
Uso básico
localStorage.setItem('theme', 'dark');
const theme = localStorage.getItem('theme');
localStorage.removeItem('theme');
localStorage.clear();
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
console.log(key, localStorage.getItem(key));
}
Diferencias clave
| Característica |
localStorage |
sessionStorage |
| Duración |
Permanente |
Se borra al cerrar pestaña |
| Alcance |
Compartido entre pestañas del mismo origen |
Solo pestaña actual |
| Uso típico |
Preferencias de usuario, tokens |
Borradores de formularios, estado de página |
Detección de capacidad
function testLocalStorageQuota() {
const testKey = '__quota_test__';
let data = '';
try {
for (let i = 0; i < 1024; i++) data += 'a'.repeat(1024);
for (let mb = 1; mb < 20; mb++) {
localStorage.setItem(testKey, data.repeat(mb));
}
} catch (e) {
console.log('localStorage quota exceeded');
} finally {
localStorage.removeItem(testKey);
}
}
Advertencias
- Bloqueo síncrono: La lectura/escritura bloquea el hilo principal; datos grandes afectan el rendimiento
- Solo cadenas: Los objetos necesitan
JSON.stringify/JSON.parse
- Evento storage: Dispara el evento
storage en otras pestañas del mismo origen al modificarse
IndexedDB
Uso básico
const openRequest = indexedDB.open('myDatabase', 1);
openRequest.onupgradeneeded = (event) => {
const db = event.target.result;
const store = db.createObjectStore('users', { keyPath: 'id' });
store.createIndex('name', 'name', { unique: false });
};
openRequest.onsuccess = (event) => {
const db = event.target.result;
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.add({ id: 1, name: 'Alice', email: 'alice@example.com' });
store.get(1).onsuccess = (e) => {
console.log(e.target.result);
};
};
Características avanzadas
| Característica |
Descripción |
| Transacciones |
Garantiza atomicidad, separación lectura/escritura |
| Índices |
Consultas eficientes en campos no clave |
| Cursores |
Iterar grandes conjuntos de datos sin cargarlos todos a la vez |
| Almacenamiento Blob |
Almacenar objetos File y Blob directamente |
| Acceso desde Worker |
Disponible en Service Workers y Web Workers |
Simplificado con la biblioteca idb
import { openDB } from 'idb';
const db = await openDB('myDatabase', 1, {
upgrade(db) {
db.createObjectStore('users', { keyPath: 'id' });
}
});
await db.put('users', { id: 1, name: 'Alice' });
const user = await db.get('users', 1);
const allUsers = await db.getAll('users');
await db.delete('users', 1);
Cache API
Uso básico
const cache = await caches.open('v1');
await cache.put('/api/data', new Response(JSON.stringify({ foo: 'bar' })));
const response = await cache.match('/api/data');
const data = await response.json();
await cache.delete('/api/data');
Con Service Worker
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
if (cached) return cached;
return fetch(event.request).then((response) => {
const clone = response.clone();
caches.open('v1').then((cache) => {
cache.put(event.request, clone);
});
return response;
});
})
);
});
Comparación de estrategias de caché
| Estrategia |
Descripción |
Caso de uso |
| Cache First |
Caché primero, red si no existe |
Recursos estáticos, fuentes |
| Network First |
Red primero, caché como respaldo |
Datos de API, contenido actualizado frecuentemente |
| Stale While Revalidate |
Devolver caché, actualizar en segundo plano |
APIs no críticas, imágenes |
| Cache Only |
Solo caché |
Páginas offline, recursos pre-cacheados |
| Network Only |
Solo red |
Solicitudes no GET, datos en tiempo real |
OPFS (Origin Private File System)
Uso básico
const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle('data.bin', { create: true });
const writable = await fileHandle.createWritable();
await writable.write(new Uint8Array([1, 2, 3, 4]));
await writable.close();
const file = await fileHandle.getFile();
const buffer = await file.arrayBuffer();
Acceso síncrono en Workers
// En un Worker — OPFS síncrono (extremadamente rápido)
const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle('large.bin', { create: true });
const accessHandle = await fileHandle.createSyncAccessHandle();
const buffer = new ArrayBuffer(4);
accessHandle.write(buffer, { at: 0 });
accessHandle.flush();
accessHandle.close();
OPFS vs File System Access API
| Característica |
OPFS |
File System Access API |
| Permiso de usuario |
No requerido |
El usuario debe seleccionar directorio |
| Visibilidad |
Sistema de archivos virtual interno del navegador |
Sistema de archivos real del SO |
| Sincronización en Worker |
Soportado |
No soportado |
| Persistencia |
Permanente |
Depende del permiso del usuario |
| Caso de uso |
Procesamiento de archivos temporales de alto rendimiento |
Editores de archivos, exportación |
Cuota de almacenamiento y persistencia
Consultar cuota disponible
const estimate = await navigator.storage.estimate();
console.log(`Used: ${(estimate.usage / 1024 / 1024).toFixed(2)} MB`);
console.log(`Quota: ${(estimate.quota / 1024 / 1024).toFixed(2)} MB`);
Solicitar persistencia
const persisted = await navigator.storage.persist();
if (persisted) {
console.log('Storage persisted, will not be auto-evicted');
} else {
console.log('Storage may be cleared under pressure');
}
Los navegadores pueden eliminar automáticamente el almacenamiento no persistente bajo presión. Llama navigator.storage.persist() para solicitar persistencia.
Guía de decisión
What do you need to store?
├── Simple key-value (< 5MB)
│ ├── Cross-tab sharing → localStorage
│ └── Current tab only → sessionStorage
├── Structured data / many records
│ └── IndexedDB
├── HTTP request/response caching
│ └── Cache API
└── Large files / binary / high-performance I/O
└── OPFS
Ejemplos del mundo real
| Escenario |
Almacenamiento recomendado |
Razón |
| Preferencia de tema del usuario |
localStorage |
Clave-valor simple, entre páginas |
| Borrador de formulario |
sessionStorage |
Temporal, limitado a la pestaña |
| Datos de herramienta offline |
IndexedDB |
Estructurado, consultas indexadas |
| Caché de recursos estáticos |
Cache API |
Diseñado para Request/Response |
| Archivos temporales de procesamiento de video |
OPFS |
E/S síncrona en Worker, alto rendimiento |
Consideraciones de seguridad
- Riesgo XSS: localStorage es legible por XSS — nunca almacenes tokens sensibles
- Cookies HttpOnly: Los tokens de autenticación deben usar cookies HttpOnly + Secure
- Cifrado de almacenamiento: Cifra datos sensibles con JWT o Hash antes de almacenarlos
- Aislamiento de mismo origen: Todo el almacenamiento sigue la política de mismo origen; el acceso entre orígenes está bloqueado
Resumen
El almacenamiento del navegador ha evolucionado desde un único localStorage hasta un sistema completo que abarca pares clave-valor, datos estructurados, caché HTTP y sistemas de archivos. La clave para la selección es coincidir con las características de los datos: configuración simple → localStorage, datos estructurados → IndexedDB, caché HTTP → Cache API, E/S de archivos de alto rendimiento → OPFS.
Usa el Analizador de Cookies para inspeccionar la configuración de cookies, la Herramienta JWT para verificar la seguridad de tokens, y la Herramienta Hash para hashear datos sensibles.