API Failure Resilience Using CDN and IndexedDB Caching
The article presents a comprehensive strategy for handling API outages by storing data locally with IndexedDB, synchronizing updates through a CDN, and implementing Axios interceptors and Node‑based scheduled jobs to ensure seamless user experience without white‑screen failures.
Introduction
What happens if an API goes down? The page turns white. A simple, non‑intrusive solution is to cache the data locally.
Thought Process
Where to Store?
The first instinct is browser local storage, considering the four main storage options.
Feature Comparison
Feature
Cookie
localStorage
sessionStorage
IndexedDB
Data lifecycle
Can be set by server or client, has expiration
Persistent
Cleared when page is closed
Persistent
Storage size
4KB
5MB
5MB
Dynamic, >250MB
Server communication
Sent in every request header
None
None
None
Compatibility
All browsers
All browsers
All browsers
Not supported by IE, supported by other major browsers
Because the required data volume exceeds 5 MB, IndexedDB is chosen.
For new users or long‑inactive users, stale or missing cache data can be an issue, so the solution also leverages cloud storage and a CDN.
In summary: online CDN + offline IndexedDB.
Overall Solution
Overall Flowchart
CDN
First, a brief overview of the online CDN.
Usually the backend handles updates; the core issue is the update strategy, which is omitted here.
Another approach is to run a dedicated Node service that updates CDN data.
Flowchart
Hijacking Logic
All API calls are intercepted; the response status and cache tags are examined to decide whether to update data, fetch data, or apply a cache strategy.
A whitelist controls which APIs are cached.
axios.interceptors.response.use(
async (resp) => {
const { config } = resp
const { url } = config
// If a CDN tag exists and the API is whitelisted, update CDN
if (this.hasCdnTag() && this.isWhiteApi(url)) {
this.updateCDN(config, resp)
}
return resp;
},
async (err) => {
const { config } = err
const { url } = config
// If the request matches the whitelist and cache is enabled, try CDN cache
if (this.isWhiteApi(url) && this.useCache()) {
return this.fetchCDN(config).then(res => {
pushLog(`CDN cache hit, please handle`, SentryTypeEnum.error)
return res
}).catch(() => {
pushLog(`CDN cache not synchronized, please handle`, SentryTypeEnum.error)
})
}
}
);Cache Strategy
When the number of consecutive API errors reaches maxCount , the cache switch is turned on and will be turned off after expiresSeconds seconds.
The switch prevents network fluctuations from causing unwanted cache hits.
/*
* Cache strategy
*/
useCache = () => {
if (this.expiresStamp > +new Date()) {
const d = new Date(this.expiresStamp)
console.warn(`
---------------------------------------
Cache enabled
Expiration time: ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}
---------------------------------------
`)
return true
}
this.errorCount += 1
localStorage.setItem(CACHE_ERROR_COUNT_KEY, `${this.errorCount}`)
if (this.errorCount > this.maxCount) {
this.expiresStamp = +new Date() + this.expiresSeconds * 1000
this.errorCount = 0
localStorage.setItem(CACHE_EXPIRES_KEY, `${this.expiresStamp}`)
localStorage.removeItem(CACHE_ERROR_COUNT_KEY)
return true
}
return false
}Unique Identifier
The combination of method , url , and data uniquely identifies an API request.
Dynamic identifiers such as timestamps can be filtered manually.
/**
* Generate a unique cache key for an API request
*/
generateCacheKey = (config) => {
const { method, url, data, params } = config;
let rawData = ''
if (method === 'get') {
rawData = params
}
if (method === 'post') {
rawData = JSON.parse(data)
}
return `${encodeURIComponent([method, url, stringify(rawData)].join('_'))}.json`;
};Update Data
/**
* Update CDN cache data
*/
updateCDN = (config, data) => {
const fileName = this.generateCacheKey(config)
const cdnUrl = `${this.prefix}/${fileName}`
axios.post(`${this.nodeDomain}/cdn/update`, {
cdnUrl,
data
})
}Node Scheduled Task
A scheduled job uses puppeteer to visit the site with a cache tag and update CDN data.
import schedule from 'node-schedule';
const scheduleJob = {};
export const xxxJob = (ctx) => {
const { xxx } = ctx.config;
ctx.logger.info(xxx, 'xxx');
const { key, url, rule } = xxx;
if (scheduleJob[key]) {
scheduleJob[key].cancel();
}
scheduleJob[key] = schedule.scheduleJob(rule, async () => {
ctx.logger.info(url, new Date());
await browserIndex(ctx, url);
});
};
export const browserIndex = async (ctx, domain) => {
ctx.logger.info('browser --start', domain);
if (!domain) {
ctx.logger.error('domain is empty');
return false;
}
const browser = await puppeteer.launch({
args: [
'--use-gl=egl',
'--disable-gpu',
'--no-sandbox',
'--disable-setuid-sandbox',
],
executablePath: process.env.CHROMIUM_PATH,
headless: true,
timeout: 0,
});
const page = await browser.newPage();
await page.goto(`${domain}?${URL_CACHE_KEY}`);
await sleep(10000);
// Click all tab items to trigger requests
const list = await page.$$('.po-tabs__item');
if (list?.length) {
for (let i = 0; i < list.length; i++) {
await list[i].click();
}
}
await browser.close();
ctx.logger.info('browser --finish', domain);
return true;
};Effect
Manually block the entire domain and the page still renders correctly.
IndexedDB
With CDN handling the online side, IndexedDB takes care of offline storage. For simple CRUD operations, the third‑party library localForage is sufficient.
axios.interceptors.response.use(
async (resp) => {
const { config } = resp
const { url } = config
if (this.hasCdnTag() && this.isWhiteApi(url)) {
this.updateCDN(config, resp)
}
if (this.isIndexDBWhiteApi(url)) {
this.updateIndexDB(config, resp)
}
return resp;
},
async (err) => {
const { config } = err
const { url } = config
if (this.isWhiteApi(url) && this.useCache()) {
return this.fetchCDN(config).then(res => {
pushLog(`CDN cache hit, please handle`, SentryTypeEnum.error)
return res
}).catch(() => {
pushLog(`CDN cache not synchronized, please handle`, SentryTypeEnum.error)
if (this.isIndexDBWhiteApi(url)) {
return this.fetchIndexDB(config).then(res => {
pushLog(`IndexedDB cache hit, please handle`, SentryTypeEnum.error)
return res
}).catch(() => {
pushLog(`IndexedDB cache not synchronized, please handle`, SentryTypeEnum.error)
})
}
})
}
}
);Conclusion
Advantages: non‑intrusive to business code, low cost, and effectively prevents pure white‑screen scenarios. Disadvantages: limited applicability, not suitable for highly time‑sensitive data, and does not support IE.
The API resilience solution is still evolving; feedback and discussion are welcome.
Its purpose is to proactively prevent API service failures, reducing a P0 incident to P2/P3 or even making it invisible to users.
Two Requests
If you found this article helpful, please click "Watch" to increase its visibility and follow the "Zhengcai Cloud Technology" public account for more curated content.
Recruitment
The Zhengcai Cloud Technology team (Zero) in Hangzhou is a passionate, growth‑oriented front‑end team with over 80 members, averaging 27 years of age, many of whom are full‑stack engineers. We work on material systems, engineering platforms, intelligent platforms, performance, cloud applications, data analysis, error monitoring, and visualization, constantly exploring the front‑end technology frontier.
If you are interested in joining a dynamic team that values technical craftsmanship and impact, please reach out to [email protected] .
政采云技术
ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.