#headless#nextjs#sitecore#performance#sitecore jss

Client-Side API Caching with IndexedDB in Sitecore XM Cloud

March 5, 2026·4 min read

In many Sitecore XM Cloud projects built with Next.js, components often call the same APIs or GraphQL queries multiple times.

For example:

  • When a page loads
  • When navigating between pages
  • When components re-render

Each time this happens, the browser sends the same request again. This leads to:

  • Extra network requests
  • Slower page performance
  • Increased API usage

In one of my projects, we noticed the same API being called repeatedly even though the data didn’t change frequently. To improve performance, we implemented client-side API caching.

Instead of calling the API every time, we store the response in the browser and reuse it when needed.

Why Not Use localStorage?

Many developers use localStorage for caching, but it has some limitations:

Storage TypePersistentAsyncCapacity
In-MemoryVery small
localStorage~5 MB
IndexedDBLarge

The biggest problems with localStorage are:

  • It is synchronous, which can block the main thread.
  • It has very limited storage capacity.
  • It’s not ideal for storing structured data.

A better alternative is IndexedDB.

IndexedDB is:

  • Asynchronous
  • Persistent across page reloads
  • Able to store large structured data

Because of these benefits, IndexedDB works very well as a client-side cache for API responses.


Simple IndexedDB Cache Implementation

To keep things reusable, we can create a small utility.

Step 1: Define Database Config

typescript
const DB_NAME = "AppCache";
const STORE_NAME = "api-cache";
const DB_VERSION = 1;
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

Step 2: Initialize the Database

typescript
let dbPromise: Promise<IDBDatabase> | null = null;
function initDB(): Promise<IDBDatabase> {
	if (dbPromise) return dbPromise;
	dbPromise = new Promise((resolve, reject) => {
		if (typeof window === "undefined") {
			reject("IndexedDB only works in browser");
			return;
		}
		const request = indexedDB.open(DB_NAME, DB_VERSION);
		request.onerror = () => reject(request.error);
		request.onsuccess = () => resolve(request.result);
		request.onupgradeneeded = () => {
			const db = request.result;
			if (!db.objectStoreNames.contains(STORE_NAME)) {
				db.createObjectStore(STORE_NAME);
			}
		};
	});
	return dbPromise;
}

Step 3: Get Data From Cache

typescript
export async function getCache(key: string) {
	const db = await initDB();
	return new Promise<any>((resolve, reject) => {
		const request = db.transaction(STORE_NAME, "readonly").objectStore(STORE_NAME).get(key);
		request.onsuccess = () => {
			const result = request.result;
			if (!result) {
				resolve(null);
				return;
			}
			const { data, timestamp } = result;
			if (Date.now() - timestamp > CACHE_TTL) {
				resolve(null);
				return;
			}
			resolve(data);
		};
		request.onerror = () => reject(request.error);
	});
}

Step 4: Save Data to Cache

typescript
export async function setCache(key: string, data: any) {
	const db = await initDB();
	return new Promise((resolve, reject) => {
		const request = db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).put(
			{
				data,
				timestamp: Date.now(),
			},
			key,
		);
		request.onsuccess = () => resolve(true);
		request.onerror = () => reject(request.error);
	});
}

Using the Cache in an API Call

Now we can easily use this cache in any API call.

typescript
import { getCache, setCache } from "@/utils/idbCache";
async function fetchProducts() {
	const cacheKey = "products"; // Check cache first
	const cachedData = await getCache(cacheKey);
	if (cachedData) {
		return cachedData;
	} // Fetch from API
	const response = await fetch("/api/products");
	const data = await response.json(); // Store in cache
	await setCache(cacheKey, data);
	return data;
}

Now the flow becomes:

1. Check if data exists in cache
2. If yes → return cached data
3. If not → call API
4. Store response in cache
5. Return data

This significantly reduces repeated API calls.


Performance Improvement

After implementing this in our project, we saw:

  • Fewer repeated API calls
  • Faster navigation
  • Better user experience

This approach works especially well for APIs that don’t change frequently.

Using IndexedDB for client-side API caching is a simple but powerful way to improve performance in Sitecore XM Cloud + Next.js applications.

It allows you to:

  • Reduce unnecessary API calls
  • Improve page load speed
  • Lower backend load

With a small reusable utility, you can easily add caching to any API in your application.

Thank you! happy coding