chore: update CHANGELOG.md to include smarter data fetching with request deduplication and caching for improved performance

This commit is contained in:
Usman Baig
2026-02-27 09:13:29 +01:00
parent 3efd23b386
commit 36774cc995
3 changed files with 102 additions and 19 deletions

View File

@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
### Added
- **Smarter data fetching.** Your dashboard now automatically prevents duplicate requests when multiple components ask for the same data at the same time. It also briefly caches recent responses, so switching between pages feels instant while still keeping everything up to date. This reduces server load and makes the app feel snappier.
- **Smarter dashboard updates.** Your dashboard now knows when you're actively viewing it versus when it's in the background. When you switch to another tab, we intelligently slow down data refreshes to save resources, then instantly catch up when you return. This keeps your analytics current without putting unnecessary load on the system.
- **Instant real-time visitor counts.** Your dashboard's "current visitors" counter now updates lightning-fast using an optimized tracking system. Instead of scanning your entire database, we maintain a live session index that shows active visitors in milliseconds—even when thousands of people are browsing your sites simultaneously.
- **More accurate visitor tracking.** We've upgraded how we identify unique visitors to ensure your analytics are always precise, even during the busiest traffic spikes. Every visitor now gets a truly unique identifier that never overlaps with others, eliminating rare edge cases where visitor counts could be slightly off.

View File

@@ -59,21 +59,46 @@ function onRefreshFailed(err: unknown) {
}
/**
* Base API client with error handling
* Base API client with error handling, request deduplication, and short-term caching
*/
async function apiRequest<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
// * Skip deduplication for non-GET requests (mutations should always execute)
const method = options.method || 'GET'
const shouldDedupe = method === 'GET'
if (shouldDedupe) {
// * Clean up expired entries periodically
if (pendingRequests.size > 100 || responseCache.size > 100) {
cleanupExpiredEntries()
}
const requestKey = getRequestKey(endpoint, options)
// * Check if we have a recent cached response (within 2 seconds)
const cached = responseCache.get(requestKey) as CachedResponse<T> | undefined
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
return cached.data
}
// * Check if there's an identical request in flight
const pending = pendingRequests.get(requestKey) as PendingRequest<T> | undefined
if (pending && Date.now() - pending.timestamp < 30000) {
return pending.promise
}
}
// * Determine base URL
const isAuthRequest = endpoint.startsWith('/auth')
const baseUrl = isAuthRequest ? AUTH_API_URL : API_URL
// * Handle legacy endpoints that already include /api/ prefix
const url = endpoint.startsWith('/api/')
const url = endpoint.startsWith('/api/')
? `${baseUrl}${endpoint}`
: `${baseUrl}/api/v1${endpoint}`
const headers: HeadersInit = {
'Content-Type': 'application/json',
...options.headers,
@@ -86,22 +111,24 @@ async function apiRequest<T>(
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)
const signal = options.signal ?? controller.signal
let response: Response
try {
response = await fetch(url, {
...options,
headers,
credentials: 'include', // * IMPORTANT: Send cookies
signal,
})
clearTimeout(timeoutId)
} catch (e) {
clearTimeout(timeoutId)
if (e instanceof Error && (e.name === 'AbortError' || e.name === 'TypeError')) {
throw new ApiError(AUTH_ERROR_MESSAGES.NETWORK, 0)
// * Create the request promise
const requestPromise = (async (): Promise<T> => {
let response: Response
try {
response = await fetch(url, {
...options,
headers,
credentials: 'include', // * IMPORTANT: Send cookies
signal,
})
clearTimeout(timeoutId)
} catch (e) {
clearTimeout(timeoutId)
if (e instanceof Error && (e.name === 'AbortError' || e.name === 'TypeError')) {
throw new ApiError(AUTH_ERROR_MESSAGES.NETWORK, 0)
}
throw e
}
throw e
}
if (!response.ok) {
if (response.status === 401) {
@@ -182,6 +209,38 @@ async function apiRequest<T>(
}
return response.json()
})()
// * For GET requests, track the promise for deduplication and cache the result
if (shouldDedupe) {
const requestKey = getRequestKey(endpoint, options)
// * Store in pending requests
pendingRequests.set(requestKey, {
promise: requestPromise as Promise<unknown>,
timestamp: Date.now(),
})
// * Clean up pending request and cache the result when done
requestPromise
.then((data) => {
// * Cache successful response
responseCache.set(requestKey, {
data,
timestamp: Date.now(),
})
// * Remove from pending
pendingRequests.delete(requestKey)
return data
})
.catch((error) => {
// * Remove from pending on error too
pendingRequests.delete(requestKey)
throw error
})
}
return requestPromise
}
export const authFetch = apiRequest

23
package-lock.json generated
View File

@@ -31,6 +31,7 @@
"react-simple-maps": "^3.0.0",
"recharts": "^2.15.0",
"sonner": "^2.0.7",
"swr": "^2.3.3",
"xlsx": "^0.18.5"
},
"devDependencies": {
@@ -10356,6 +10357,19 @@
"node": ">=12.0.0"
}
},
"node_modules/swr": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.4.0.tgz",
"integrity": "sha512-sUlC20T8EOt1pHmDiqueUWMmRRX03W7w5YxovWX7VR2KHEPCTMly85x05vpkP5i6Bu4h44ePSMD9Tc+G2MItFw==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.3",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/tailwind-merge": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz",
@@ -11113,6 +11127,15 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",