> ## Documentation Index
> Fetch the complete documentation index at: https://docs.uselayers.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Caching

> How the Layers SDK handles caching, request deduplication, and persistence to local storage so your storefront feels fast and avoids duplicate API calls.

The SDK automatically caches requests to minimize API calls and improve performance.

## Request deduplication

When multiple components request the same data, the SDK automatically deduplicates requests:

```typescript theme={null}
const collection1 = client.collection({ handle: 'shirts' })
const collection2 = client.collection({ handle: 'shirts' })

await Promise.all([
  collection1.execute(),
  collection2.execute(),
])

// Only ONE network request is made
```

## Local storage restoration

Cached results are restored from local storage on back navigation:

```typescript theme={null}
// Initial page load
const collection = client.collection({ handle: 'shirts' })
await collection.execute() // Makes API call, caches result

// User navigates away and returns via back button
// Cache is restored from localStorage - no API call needed
```

Disable auto-restore if needed:

```typescript theme={null}
const { data: client } = createClient({
  token: 'your-token',
  sorts: [{ name: 'Featured', code: 'featured' }],
  facets: [{ name: 'Color', code: 'options.color' }],
  restoreCache: false,
})
```

## Cache invalidation

Invalidate cached queries by pattern:

```typescript theme={null}
const { cache } = client

// Invalidate all browse queries
cache.invalidate('browse')

// Invalidate specific search
cache.invalidate('search:ring')

// Clear all cache
cache.clear()
```

## Search cache

The prepare endpoint caches expensive operations (embeddings, query expansions):

```typescript theme={null}
const search = client.search()

await search.prepare({ query: 'ring' })

// Multiple executes reuse the prepare cache
await search.execute({ query: 'ring', page: 1 })
await search.execute({ query: 'ring', page: 2 })
```

When using `search_id`, the server maintains the cache for 15 minutes:

```typescript theme={null}
const prepareResult = await search.prepare({ query: 'ring' })
const searchId = prepareResult.data?.searchId

await search.execute({ 
  query: 'ring',
  searchId,
})
```

## Storage adapters

Cache persistence is pluggable via a `StorageAdapter`. The SDK ships two adapters and accepts any custom implementation, so the cache survives reloads in the browser, page navigations in SSR frameworks, or process restarts in Node.

| Adapter                    | Environment                                  | Import                 |
| :------------------------- | :------------------------------------------- | :--------------------- |
| `localStorageAdapter(key)` | Browser (default)                            | `@commerce-blocks/sdk` |
| `fileStorage(path, fs)`    | Node.js / build scripts                      | `@commerce-blocks/sdk` |
| Custom `StorageAdapter`    | Any (sessionStorage, IndexedDB, Redis, etc.) | Bring your own         |

### Browser (localStorage)

`localStorage` is used automatically when the SDK runs in a browser. Pass an explicit adapter only if you want to customize the storage key:

```typescript theme={null}
import { createClient, localStorageAdapter } from '@commerce-blocks/sdk'

const { data: client } = createClient({
  token: 'your-token',
  sorts: [{ name: 'Featured', code: 'featured' }],
  facets: [{ name: 'Color', code: 'options.color' }],
  storage: localStorageAdapter('shop:cache:v1'),
})
```

`localStorageAdapter` returns `null` in environments without `window.localStorage` (e.g. SSR), so the SDK silently falls back to in-memory caching — safe to use in isomorphic code.

### Node.js (file system)

Use `fileStorage` to persist the cache to disk for build scripts, server-rendered pages, or long-running Node processes:

```typescript theme={null}
import fs from 'node:fs'
import { createClient, fileStorage } from '@commerce-blocks/sdk'

const { data: client } = createClient({
  token: process.env.LAYERS_TOKEN!,
  sorts: [{ name: 'Featured', code: 'featured' }],
  facets: [{ name: 'Color', code: 'options.color' }],
  storage: fileStorage('./.cache/layers.json', fs),
})
```

The adapter takes a minimal `FileSystem` interface (`readFileSync`, `writeFileSync`, `unlinkSync`), so you can substitute a mock in tests or wrap an alternative file system.

### Custom adapter

Implement `StorageAdapter` to back the cache with anything else — `sessionStorage`, IndexedDB, Redis, Cloudflare KV, an HTTP service, etc.

```typescript theme={null}
import type { StorageAdapter } from '@commerce-blocks/sdk'

const sessionAdapter: StorageAdapter = {
  read: () => sessionStorage.getItem('layers:cache'),
  write: (data) => sessionStorage.setItem('layers:cache', data),
  remove: () => sessionStorage.removeItem('layers:cache'),
}

const { data: client } = createClient({
  token: 'your-token',
  sorts: [{ name: 'Featured', code: 'featured' }],
  facets: [{ name: 'Color', code: 'options.color' }],
  storage: sessionAdapter,
})
```

All three adapter methods are synchronous and operate on a single serialized JSON string. Wrap async backends in a sync queue or memoize the latest snapshot if you need IndexedDB or remote storage.

See [Cache, storage, and signals](/sdk/api-reference/cache-and-storage) for the full interface reference.

## Fetch adapter

The SDK calls the global `fetch` by default. Provide a custom implementation via the `fetch` option for SSR runtimes, testing, or environments where `fetch` is unavailable or blocked.

```typescript theme={null}
import type { CustomFetch } from '@commerce-blocks/sdk'

type CustomFetch = (url: string, init?: RequestInit) => Promise<Response>
```

### xhrFetch (browser fallback)

`xhrFetch` is an `XMLHttpRequest`-based adapter shipped with the SDK. Use it when ad blockers, browser extensions, or restrictive network policies intercept `fetch` calls:

```typescript theme={null}
import { createClient, xhrFetch } from '@commerce-blocks/sdk'

const { data: client } = createClient({
  token: 'your-token',
  sorts: [{ name: 'Featured', code: 'featured' }],
  facets: [{ name: 'Color', code: 'options.color' }],
  fetch: xhrFetch,
})
```

### Custom fetch

Any function matching the `CustomFetch` signature can be passed. Common uses:

* **SSR / Node**: inject a Node-compatible fetch (e.g. `undici`, `node-fetch`) on older runtimes.
* **Testing**: stub responses without intercepting global `fetch`.
* **Instrumentation**: add tracing, retries, or auth headers around the request.

```typescript theme={null}
const tracedFetch: CustomFetch = async (url, init) => {
  const start = performance.now()
  try {
    return await fetch(url, init)
  } finally {
    console.log(`[layers] ${init?.method ?? 'GET'} ${url} (${performance.now() - start}ms)`)
  }
}

const { data: client } = createClient({
  token: 'your-token',
  sorts: [{ name: 'Featured', code: 'featured' }],
  facets: [{ name: 'Color', code: 'options.color' }],
  fetch: tracedFetch,
})
```

The SDK handles retries, abort signals, and error classification on top of whatever `fetch` returns — your adapter only needs to issue the request and return a `Response`.

## Next steps

<CardGroup cols={2}>
  <Card title="Performance" icon="gauge-high" href="/sdk/performance">
    Learn performance optimization techniques.
  </Card>

  <Card title="Best Practices" icon="circle-check" href="/sdk/best-practices">
    Review SDK best practices and common pitfalls.
  </Card>
</CardGroup>
