Skip to main content

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.

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:
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:
// 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:
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:
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):
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:
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.
AdapterEnvironmentImport
localStorageAdapter(key)Browser (default)@commerce-blocks/sdk
fileStorage(path, fs)Node.js / build scripts@commerce-blocks/sdk
Custom StorageAdapterAny (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:
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:
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.
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 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.
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:
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.
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

Performance

Learn performance optimization techniques.

Best Practices

Review SDK best practices and common pitfalls.