Skip to content

Fallback Chains

Fallback chains allow you to use multiple storage adapters together. This is useful for migration scenarios, redundancy, or progressive enhancement.

How It Works

When you pass an array of adapters to createStorageStore:

  • Read: Returns the first non-null value found (in order)
  • Write: Writes to ALL adapters
  • Listen: Listens to the FIRST adapter only
  • Remove: Removes from ALL adapters
import {
createStorageStore,
sessionStorageAdapter,
localStorageAdapter,
} from "@vp-tw/nanostores-storage";
const store = createStorageStore(
[sessionStorageAdapter, localStorageAdapter], // Fallback chain
"user-data",
);

Use Cases

Session with Persistence

Store data in sessionStorage (fast, tab-isolated) with localStorage backup:

const store = createStorageStore([sessionStorageAdapter, localStorageAdapter], "draft-content");
// Read: Prefers sessionStorage, falls back to localStorage
// Write: Saves to both (redundancy)

Migration Strategy

When migrating from one storage to another:

// Old: data was in localStorage
// New: prefer sessionStorage
const store = createStorageStore([sessionStorageAdapter, localStorageAdapter], "legacy-key");
// Reads old data from localStorage if sessionStorage is empty
// New writes go to both, gradually migrating data

Cross-Subdomain with Local Fallback

Combine cookies (cross-subdomain) with localStorage (faster):

import { createStorageStore, localStorageAdapter } from "@vp-tw/nanostores-storage";
import { createCookieAdapter } from "./my-cookie-adapter";
const cookieAdapter = createCookieAdapter();
const store = createStorageStore([localStorageAdapter, cookieAdapter], "shared-session");

Read Order Behavior

// Scenario: sessionStorage is empty, localStorage has "cached-value"
const store = createStorageStore([sessionStorageAdapter, localStorageAdapter], "my-key");
store.get(); // Returns "cached-value" (from localStorage)
// After setting a value:
store.set("new-value");
// Both now have "new-value"
sessionStorage.getItem("my-key"); // "new-value"
localStorage.getItem("my-key"); // "new-value"

Listening Behavior

With listen: true, the store only reacts to changes from the first adapter in the chain:

const store = createStorageStore([sessionStorageAdapter, localStorageAdapter], "my-key", {
listen: true,
});
// Only sessionStorage changes trigger store updates
// localStorage changes do NOT trigger updates

This design is intentional for the primary use case: maintaining tab-isolated state with a persistent fallback.

Example: [sessionStorage, localStorage]

  • Same tab: Only listens to sessionStorage (per-tab isolation)
  • New tab: sessionStorage is empty → reads from localStorage
  • Write: Saves to both adapters

This ensures each tab maintains its own state while new tabs start with the last persisted value.

Important Notes

  1. Write Order: Values are written to adapters in array order
  2. Atomic Operations: There’s no transaction — if one adapter fails, others may succeed
  3. Performance: More adapters = more I/O operations
  4. Storage Limits: Each adapter has its own limits

Example: Complete Setup

import {
createStorageStore,
sessionStorageAdapter,
localStorageAdapter,
} from "@vp-tw/nanostores-storage";
// User preferences with session override
const prefsStore = createStorageStore(
[sessionStorageAdapter, localStorageAdapter],
"user-preferences",
{
defaultValue: JSON.stringify({ theme: "system", language: "en" }),
listen: true,
},
);
// Helper to work with JSON
function getPrefs() {
const raw = prefsStore.get();
return raw ? JSON.parse(raw) : { theme: "system", language: "en" };
}
function setPrefs(prefs: { theme: string; language: string }) {
prefsStore.set(JSON.stringify(prefs));
}

Next Steps