Client-Side API Caching with IndexedDB in Sitecore XM Cloud
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 Type | Persistent | Async | Capacity |
|---|---|---|---|
| In-Memory | ❌ | ✅ | Very small |
| localStorage | ✅ | ❌ | ~5 MB |
| IndexedDB | ✅ | ✅ | Large |
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
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
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
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
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.
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