localStorage y sessionStorage son las dos formas de almacenar datos en el navegador del usuario sin necesidad de servidor. Son simples, sincrónicas y bien soportadas, pero tienen limitaciones importantes que hay que conocer antes de usarlas en producción.
localStorage vs sessionStorage: la diferencia clave
Ambas APIs tienen exactamente la misma interfaz. La diferencia es el ciclo de vida de los datos:
- localStorage: persiste entre sesiones. Los datos sobreviven al cierre del navegador y se comparten entre pestañas del mismo origen.
- sessionStorage: dura solo mientras la pestaña está abierta. Se crea nueva por pestaña, incluso del mismo origen, y no se comparte entre ellas.
// setItem y getItem
localStorage.setItem('tema', 'oscuro');
const tema = localStorage.getItem('tema');
console.log(tema); // 'oscuro'
// Valor inexistente devuelve null (no undefined)
console.log(localStorage.getItem('inexistente')); // null
// Eliminar y limpiar
localStorage.removeItem('tema');
localStorage.clear(); // Elimina todo del localStorage de este origen
// sessionStorage: misma API
sessionStorage.setItem('token_temporal', 'abc123');
Almacenar objetos con JSON.stringify
localStorage solo almacena strings. Para guardar objetos o arrays hay que serializarlos:
// Guardar objeto
const usuario = { id: 42, nombre: 'Ana', rol: 'admin' };
localStorage.setItem('usuario', JSON.stringify(usuario));
// Recuperar objeto
const guardado = localStorage.getItem('usuario');
const usuarioRecuperado = guardado ? JSON.parse(guardado) : null;
console.log(usuarioRecuperado?.nombre); // 'Ana'
// Patrón completo con manejo de errores:
function guardarEnStorage(clave, valor) {
try {
localStorage.setItem(clave, JSON.stringify(valor));
} catch (err) {
// QuotaExceededError: el storage está lleno
console.error('Error al guardar en localStorage:', err);
}
}
function obtenerDeStorage(clave, valorPorDefecto = null) {
try {
const item = localStorage.getItem(clave);
return item !== null ? JSON.parse(item) : valorPorDefecto;
} catch (err) {
// JSON inválido u otro error
console.error('Error al leer de localStorage:', err);
return valorPorDefecto;
}
}
// Uso:
guardarEnStorage('preferencias', { idioma: 'es', notificaciones: true });
const prefs = obtenerDeStorage('preferencias', { idioma: 'es' });
Detectar cambios con el evento storage
El evento storage permite que otras pestañas del mismo origen reaccionen a cambios en localStorage (pero NO en sessionStorage, que es por pestaña):
// Esta pestaña escucha cambios hechos por OTRAS pestañas
window.addEventListener('storage', (evento) => {
console.log('Clave cambiada:', evento.key);
console.log('Valor anterior:', evento.oldValue);
console.log('Valor nuevo:', evento.newValue);
console.log('URL de la pestaña que cambió:', evento.url);
if (evento.key === 'sesion' && evento.newValue === null) {
// Otra pestaña cerró la sesión ? cerrar aquí también
redirigirALogin();
}
});
Cuándo preferir cookies
localStorage no es siempre la mejor opción. Las cookies tienen funcionalidades que localStorage no tiene:
// localStorage: // ? Más almacenamiento (5-10MB vs 4KB de cookies) // ? API más simple // ? No se envía al servidor automáticamente // ? No tiene expiración automática // ? Accesible desde JavaScript (XSS puede leerlo) // Cookies (con atributos de seguridad): // ? Se envían al servidor en cada petición HTTP // ? HttpOnly: inaccesible desde JavaScript (protege de XSS) // ? SameSite: protege de CSRF // ? Secure: solo HTTPS // ? Expiration: caducan automáticamente // Para tokens de autenticación, las cookies HttpOnly son más seguras: // document.cookie = "token=abc; HttpOnly; Secure; SameSite=Strict" // (aunque HttpOnly solo puede setearlo el servidor, no el JS) // localStorage es ideal para: // - Preferencias de usuario (tema, idioma) // - Datos de la UI que no son sensibles // - Caché de datos públicos // - Estado de la aplicación (carrito, filtros activos)
Errores frecuentes: SSR y modo incógnito
// ERROR 1: acceder a localStorage en SSR (Next.js, Nuxt...)
// El código se ejecuta en Node.js donde window no existe
// MAL:
const tema = localStorage.getItem('tema'); // ReferenceError en SSR
// BIEN: comprobar que window existe
const tema = typeof window !== 'undefined'
? localStorage.getItem('tema')
: 'claro';
// O usar useEffect en React (solo se ejecuta en el cliente):
useEffect(() => {
const temaGuardado = localStorage.getItem('tema');
if (temaGuardado) setTema(temaGuardado);
}, []);
// ERROR 2: modo incógnito con storage lleno
// En algunos navegadores en modo incógnito, localStorage puede
// lanzar QuotaExceededError incluso antes de guardar nada
// La solución es siempre envolver en try/catch (como se mostró arriba)
// Verificar disponibilidad:
function storageDisponible(tipo) {
try {
const storage = window[tipo];
const test = '__test__';
storage.setItem(test, test);
storage.removeItem(test);
return true;
} catch {
return false;
}
}
const hayLocalStorage = storageDisponible('localStorage');
La capacidad típica de localStorage es de 5MB por origen en la mayoría de navegadores. No está diseñado para guardar cantidades grandes de datos: para eso existen IndexedDB (para bases de datos en el cliente) o la Cache API (para assets de Service Workers).
