Storage and offline support

Updated: Sep 19, 2026

Web Storage

Meta Ray-Ban Display glasses support localStorage and sessionStorage for small string values:
  • localStorage can persist across page sessions.
  • sessionStorage belongs to the current browsing-context session.
Both APIs are synchronous. Keep values small, catch write failures, and treat stored values as recoverable rather than permanent. Browser data clearing, application removal, device reset, or storage pressure can remove data.

Save and read values

function savePreferences(preferences) {
  try {
    localStorage.setItem('example.preferences', JSON.stringify(preferences));
    return true;
  } catch (error) {
    console.warn('Could not save preferences:', error);
    return false;
  }
}

function loadPreferences() {
  const stored = localStorage.getItem('example.preferences');
  if (stored === null) {
    return null;
  }

  try {
    return JSON.parse(stored);
  } catch (error) {
    localStorage.removeItem('example.preferences');
    return null;
  }
}
Use removeItem() to remove one key. Reserve clear() for an origin whose complete Web Storage namespace the app intentionally owns, because it deletes all values on that origin. Apps hosted on the same origin can share site data, so namespace keys.
Use sessionStorage for temporary state that belongs only to the current page session:
sessionStorage.setItem('example.onboardingSeen', 'true');
Handle QuotaExceededError instead of relying on a fixed quota. Use Web Storage for preferences and small application state. Choose a more appropriate storage and security design for images, large datasets, secrets, bearer tokens, or sensitive personal information.

Offline support

A Service Worker and Cache Storage can serve an installed app shell during an offline cold start. The first visit still requires a network connection, and a new worker may not control the page until a later navigation or reload.
Service Workers require a secure context such as https://. They are event-driven and can be stopped between events rather than running continuously in the background.

Register the worker

Use a path relative to the app instead of assuming it is hosted at the origin root:
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('./sw.js').catch((error) => {
    console.warn('Offline support is unavailable:', error);
  });
}
Registration success does not guarantee that installation, activation, or every cache write will succeed. Keep the app usable online when registration fails.

Cache an app shell

Use an app-specific cache prefix. During cleanup, delete only previous versions owned by this app:
const CACHE_PREFIX = 'example-app-';
const CACHE_NAME = `${CACHE_PREFIX}v1`;
const APP_SHELL = [
  new URL('./', self.registration.scope).href,
  new URL('./app.js', self.registration.scope).href,
  new URL('./styles.css', self.registration.scope).href,
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL)),
  );
});

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((names) =>
      Promise.all(
        names
          .filter(
            (name) => name.startsWith(CACHE_PREFIX) && name !== CACHE_NAME,
          )
          .map((name) => caches.delete(name)),
      ),
    ),
  );
});
cache.addAll() fails the installation if any requested resource fails. Keep the precache list small and verify every URL. Limit cleanup to the app’s cache prefix because another application or library on the same origin may own other caches.

Serve cached content

Intercept only the requests the worker owns. This example uses cached responses first, goes to the network for a miss, and falls back to the cached app shell for a failed same-origin navigation:
self.addEventListener('fetch', (event) => {
  const request = event.request;
  const url = new URL(request.url);

  if (request.method !== 'GET' || url.origin !== self.location.origin) {
    return;
  }

  event.respondWith((async () => {
    const cache = await caches.open(CACHE_NAME);
    const cached = await cache.match(request);
    if (cached) {
      return cached;
    }

    try {
      return await fetch(request);
    } catch (error) {
      if (request.mode === 'navigate') {
        const shell = await cache.match(APP_SHELL[0]);
        if (shell) {
          return shell;
        }
      }
      throw error;
    }
  })());
});
Choose a strategy per resource type. Cache-first works well for versioned static assets. Network-first or stale-while-revalidate can be more appropriate for data that changes. Cache authenticated or sensitive responses only when the app has a deliberate security and invalidation design.

Updates and eviction

Changing the Service Worker file triggers an update check. A newly installed worker commonly waits until pages controlled by the old worker close. Use versioned cache names, keep old and new application code compatible during the transition, and use immediate activation only when the app has a safe handoff plan.
Cached data can be evicted or cleared. Treat the cache as a performance and offline aid, not the only copy of user-created data. Remove obsolete caches by prefix and keep the app functional when a cache entry is missing.

Connectivity and live data

navigator.onLine and the online and offline events are connectivity hints. Use actual request success and failure as the source of truth because these hints do not prove that the app’s service or a particular request is reachable.
Check HTTP status as well as network exceptions:
async function fetchData(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`Request failed with ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    showOfflineMessage('This content is not available right now.');
    return null;
  }
}
Show an offline or stale state only when it helps the user understand what can be done. Show a connection indicator only where it helps explain available behavior.

Test storage and offline behavior

Before publishing, verify:
  • first installation while online;
  • a controlled reload after the worker takes control;
  • an offline cold start with the cached shell;
  • failure when a required precache URL is missing;
  • a waiting-worker update and the next activation;
  • cleanup of only the app’s obsolete cache versions;
  • missing or evicted Web Storage and Cache Storage data;
  • QuotaExceededError and malformed stored values;
  • failed network requests and non-successful HTTP responses;
  • recovery when connectivity returns; and
  • that clearing or removing the app does not leave correctness dependent on retained local data.