Existen muchas opciones diferentes para almacenar datos en el navegador. ¿Cuál es la mejor opción para tus necesidades?
Cuando te trasladas de un lugar a otro, las conexiones de Internet pueden ser débiles o inexistentes. Por eso, ofrecer compatibilidad y un rendimiento confiable sin conexión es una característica común de las apps web progresivas. Incluso en entornos inalámbricos ideales, el uso sensato del almacenamiento en caché y otras técnicas de almacenamiento puede mejorar considerablemente la experiencia del usuario. Existen varias formas de almacenar en caché los recursos estáticos de tu aplicación (HTML, JavaScript, CSS, imágenes, etc.) y los datos (datos del usuario, artículos de noticias, etc.). Pero, ¿cuál es la mejor solución? ¿Cuánto puedes almacenar? ¿Cómo se evita que se desaloje?
¿Qué debo usar?
Esta es una recomendación general para almacenar recursos:
- Para los recursos de red necesarios para cargar tu app, usa la API de Cache Storage (parte de los service workers).
- Para el contenido basado en archivos, usa el sistema de archivos privados de Origin (OPFS).
- Para otros datos, usa IndexedDB (con un contenedor de promesas).
IndexedDB, el OPFS y la API de Cache Storage son compatibles con todos los navegadores modernos.
Son asíncronos y no bloquean el subproceso principal (pero también hay una variante síncrona de la OPFS que está disponible exclusivamente en los trabajadores web). Se puede acceder a ellos desde el objeto window
, los trabajadores web y los trabajadores del servicio, lo que permite usarlos en cualquier parte de tu código.
¿Qué ocurre con otros mecanismos de almacenamiento?
Hay varios otros mecanismos de almacenamiento disponibles en el navegador, pero su uso es limitado y pueden causar problemas de rendimiento significativos.
SessionStorage es específico de la pestaña y se limita a su vida útil. Puede ser útil para almacenar pequeñas cantidades de información específica de la sesión, por ejemplo, una clave de IndexedDB. Se debe usar con precaución porque es síncrono y bloqueará el subproceso principal. Se limita a unos 5 MB y solo puede contener cadenas. Debido a que es específico de la pestaña, no se puede acceder a él desde los trabajadores web ni los trabajadores de servicio.
Se debe evitar LocalStorage porque es síncrono y bloqueará el subproceso principal. Se limita a unos 5 MB y solo puede contener cadenas. No se puede acceder a LocalStorage desde Web Workers ni Service Workers.
Las cookies tienen sus usos, pero no deben usarse para el almacenamiento. Las cookies se envían con cada solicitud HTTP, por lo que almacenar algo más que una pequeña cantidad de datos aumentará significativamente el tamaño de cada solicitud web. Son síncronos y no se puede acceder a ellos desde los trabajadores web. Al igual que LocalStorage y SessionStorage, las cookies se limitan solo a cadenas.
La API de File System Access se diseñó para permitir que los usuarios lean y editen archivos en su sistema de archivos local. El usuario debe otorgar permiso antes de que una página pueda leer o escribir en cualquier archivo local, y los permisos no se conservan entre sesiones, a menos que se almacenen en caché en IndexedDB. La API de File System Access es más adecuada para casos de uso como editores, en los que debes abrir un archivo, modificarlo y, luego, posiblemente guardar los cambios en el archivo.
La API de File System y la API de FileWriter proporcionan métodos para leer y escribir archivos en un sistema de archivos en zona de pruebas. Si bien es asíncrono, no se recomienda porque solo está disponible en navegadores basados en Chromium.
¿Cuánto puedo almacenar?
En resumen, muchos, al menos unos doscientos megabytes y, potencialmente, cientos de gigabytes o más. Las implementaciones de navegadores varían, pero la cantidad de almacenamiento disponible suele basarse en la cantidad de almacenamiento disponible en el dispositivo.
- Chrome permite que el navegador use hasta el 80% del espacio total en el disco. Un origen puede usar hasta el 60% del espacio total en el disco. Puedes usar la API de StorageManager para determinar la cuota máxima disponible. Es posible que otros navegadores basados en Chromium sean diferentes.
- En el modo Incógnito, Chrome reduce la cantidad de almacenamiento que puede usar un origen al 5% aproximadamente del espacio total en el disco.
- Si el usuario habilitó la opción "Borrar las cookies y los datos de sitios cuando cierras todas las ventanas" en Chrome, la cuota de almacenamiento se reduce significativamente a un máximo de aproximadamente 300 MB.
- Firefox permite que el navegador use hasta un 50% del espacio libre en el disco. Un grupo de eTLD+1 (p.ej.,
example.com
,www.example.com
yfoo.bar.example.com
) pueden usar hasta 2 GB. Puedes usar la API de StorageManager para determinar cuánto espacio queda disponible. - Al parecer, Safari (para computadoras y dispositivos móviles) permite alrededor de 1 GB. Cuando se alcance el límite, Safari le pedirá al usuario que aumente el límite en incrementos de 200 MB. No pude encontrar documentación oficial sobre este tema.
- Si se agrega una AWP a la pantalla principal en Safari para dispositivos móviles, se crea un contenedor de almacenamiento nuevo y no se comparte nada entre la AWP y Safari para dispositivos móviles. Una vez que se alcanza la cuota de una AWP instalada, parece que no hay forma de solicitar almacenamiento adicional.
En el pasado, si un sitio superaba un cierto límite de datos almacenados, el navegador le solicitaba al usuario que otorgara permiso para usar más datos. Por ejemplo, si el origen usara más de 50 MB, el navegador le pedirá al usuario que le permita almacenar hasta 100 MB y, luego, volverá a preguntar en incrementos de 50 MB.
Actualmente, la mayoría de los navegadores modernos no le solicitan al usuario permiso y permiten que un sitio use hasta su cuota asignada. La excepción parece ser Safari, que solicita permiso para aumentar la cuota asignada cuando se supera la cuota de almacenamiento. Si un origen intenta usar más de su cuota asignada, fallarán los intentos posteriores de escribir datos.
¿Cómo puedo verificar cuánto almacenamiento tengo disponible?
En muchos navegadores, puedes usar la API de StorageManager para determinar la cantidad de almacenamiento disponible para el origen y cuánto almacenamiento está usando. Informa la cantidad total de bytes que usan IndexedDB y la API de Cache, y permite calcular el espacio de almacenamiento restante aproximado disponible.
if (navigator.storage && navigator.storage.estimate) {
const quota = await navigator.storage.estimate();
// quota.usage -> Number of bytes used.
// quota.quota -> Maximum number of bytes available.
const percentageUsed = (quota.usage / quota.quota) * 100;
console.log(`You've used ${percentageUsed}% of the available storage.`);
const remaining = quota.quota - quota.usage;
console.log(`You can write up to ${remaining} more bytes.`);
}
Debes detectar los errores de exceso de cuota (consulta a continuación). En algunos casos, es posible que la cuota disponible supere la cantidad real de almacenamiento disponible.
Inspeccionar
Durante el desarrollo, puedes usar DevTools de tu navegador para inspeccionar los diferentes tipos de almacenamiento y borrar todos los datos almacenados.
Se agregó una nueva función en Chrome 88 que te permite anular la cuota de almacenamiento del sitio en el panel de almacenamiento. Esta función te permite simular diferentes dispositivos y probar el comportamiento de tus apps en situaciones de baja disponibilidad de disco. Ve a Application y, luego, a Storage, habilita la casilla de verificación Simulate custom storage quota y, luego, ingresa cualquier número válido para simular la cuota de almacenamiento.
Mientras trabajaba en esta guía, escribí una herramienta simple para intentar usar rápidamente la mayor cantidad de almacenamiento posible. Es una forma rápida de experimentar con diferentes mecanismos de almacenamiento y ver qué sucede cuando usas toda tu cuota.
¿Cómo se maneja el exceso de cuota?
¿Qué debes hacer cuando superas la cuota? Lo más importante es que siempre debes detectar y controlar los errores de escritura, ya sea un QuotaExceededError
o algo más. Luego, según el diseño de tu app, decide cómo manejarlo.
Por ejemplo, borra el contenido al que no se accedió durante mucho tiempo, quita los datos según el tamaño o proporciona una forma para que los usuarios elijan lo que quieren borrar.
Tanto IndexedDB como la API de Cache arrojan un DOMError
llamado QuotaExceededError
cuando superas la cuota disponible.
IndexedDB
Si el origen superó su cuota, fallarán los intentos de escritura en IndexedDB. Se llamará al controlador onabort()
de la transacción y se pasará un evento.
El evento incluirá un DOMException
en la propiedad de error. Si verificas el error name
, se mostrará QuotaExceededError
.
const transaction = idb.transaction(['entries'], 'readwrite');
transaction.onabort = function(event) {
const error = event.target.error; // DOMException
if (error.name == 'QuotaExceededError') {
// Fallback code goes here
}
};
API de Cache
Si el origen superó su cuota, los intentos de escribir en la API de Cache se rechazarán con un DOMException
QuotaExceededError
.
try {
const cache = await caches.open('my-cache');
await cache.add(new Request('/sample1.jpg'));
} catch (err) {
if (error.name === 'QuotaExceededError') {
// Fallback code goes here
}
}
¿Cómo funciona el descarte?
El almacenamiento web se clasifica en dos grupos: "Mejor esfuerzo" y "Persistente". El mejor esfuerzo significa que el navegador puede borrar el almacenamiento sin interrumpir al usuario, pero es menos duradero para los datos a largo plazo o críticos. El almacenamiento persistente no se libera automáticamente cuando hay poco espacio. El usuario debe liberar manualmente este almacenamiento (a través de la configuración del navegador).
De forma predeterminada, los datos de un sitio (incluidos IndexedDB, Cache API, etc.) se incluyen en la categoría de mejor esfuerzo, lo que significa que, a menos que un sitio haya solicitado almacenamiento persistente, el navegador puede desalojar los datos del sitio a su discreción, por ejemplo, cuando el almacenamiento del dispositivo es bajo.
La política de expulsión para el mejor esfuerzo es la siguiente:
- Los navegadores basados en Chromium comenzarán a expulsar datos cuando se acabe el espacio, primero borrarán todos los datos del sitio del origen menos usado recientemente y, luego, del siguiente, hasta que el navegador ya no supere el límite.
- Firefox comenzará a expulsar datos cuando se complete el espacio en el disco disponible. Primero, borrará todos los datos del sitio del origen que se usó menos recientemente y, luego, del siguiente, hasta que el navegador ya no supere el límite.
- Anteriormente, Safari no expulsaba datos, pero recientemente implementó un nuevo límite de siete días en todo el almacenamiento en el que se pueden escribir (consulta a continuación).
A partir de iOS y iPadOS 13.4 y Safari 13.1 en macOS, hay un límite de siete días en todo el almacenamiento de secuencias de comandos que se puede escribir, incluido IndexedDB, el registro de service workers y la API de Cache. Esto significa que Safari expulsará todo el contenido de la caché después de siete días de uso si el usuario no interactúa con el sitio. Esta política de expulsión no se aplica a los AWP instalados que se agregaron a la pantalla principal. Consulta Bloqueo completo de cookies de terceros y mucho más en el blog de WebKit para obtener todos los detalles.
Buckets de almacenamiento
La idea principal de la API de Storage Buckets es otorgar a los sitios la capacidad de crear varios buckets de almacenamiento, en los que el navegador puede borrar cada bucket independientemente de los demás. Esto permite a los desarrolladores especificar la priorización de expulsión para asegurarse de que no se borren los datos más valiosos.
Bono: Por qué usar un wrapper para IndexedDB
IndexedDB es una API de bajo nivel que requiere una configuración significativa antes de su uso, lo que puede ser particularmente difícil para almacenar datos de baja complejidad. A diferencia de la mayoría de las APIs modernas basadas en promesas, se basa en eventos. Los wrappers de promesas, como idb para IndexedDB, ocultan algunas de las funciones potentes, pero lo más importante es que ocultan la maquinaria compleja (p.ej., transacciones y versiones de esquemas) que se incluye con la biblioteca de IndexedDB.
Bonificación: SQLite Wasm
Después de que Web SQL dejó de estar disponible y se quitó de Chrome, Google trabajó con los encargados de la popular base de datos SQLite para ofrecer un reemplazo de Web SQL basado en SQLite. Lee SQLite Wasm en el navegador respaldado por el sistema de archivos privados de Origin para obtener detalles sobre cómo usarlo.
Conclusión
Atrás quedaron los días del almacenamiento limitado y de pedirle al usuario que almacene cada vez más datos. Los sitios pueden almacenar de manera eficaz todos los recursos y datos que necesitan para ejecutarse. Con la API de StorageManager, puedes determinar cuánto tienes disponible y cuánto usaste. Además, con el almacenamiento persistente, puedes protegerlo de la expulsión, a menos que el usuario lo quite.
Recursos adicionales
Gracias
Agradecemos especialmente a Jarryd Goodman, Phil Walton, Eiji Kitamura, Daniel Murphy, Darwin Huang, Josh Bell, Marijn Kruisselbrink y Victor Costan por revisar esta guía. Gracias a Eiji Kitamura, Addy Osmani y Marc Cohen, quienes escribieron los artículos originales en los que se basa este artículo. Eiji escribió una herramienta útil llamada Browser Storage Abuser que fue útil para validar el comportamiento actual. Te permite almacenar tantos datos como sea posible y ver los limites de almacenamiento en tu navegador. Gracias a François Beaufort, quien analizó Safari para determinar sus límites de almacenamiento, y a Thomas Steiner por agregar información sobre el sistema de archivos privados de origen, los buckets de almacenamiento, SQLite Wasm y una actualización general del contenido en 2024.
La imagen hero es de Guillaume Bolduc en Unsplash.