Availability data refreshed from server on every calendar view load
Why it matters
Serving stale availability data is a direct cause of double-bookings. When a customer books slot A, that change must be visible to every other client on the next calendar load — not after a session refresh or browser restart. Client-side caches without TTLs violate CWE-362 (concurrent resource access) and ISO 25010 functional correctness: two users see the same slot as open and both confirm. The downstream effect is a support ticket, a refund, and a damaged reputation. Booking platforms live and die by schedule integrity; a single double-booking erodes trust faster than any competitor can.
Severity rationale
Critical because stale availability displayed to the user is the direct mechanical cause of double-bookings, which are unrecoverable without manual intervention and refunds.
Remediation
Add month and year (or equivalent time-period identifiers) to the useEffect dependency array in your calendar component so a fresh fetch fires on every navigation, not just on mount. Include a cache-buster parameter or set Cache-Control: no-cache on the server endpoint.
useEffect(() => {
fetch(`/api/availability?month=${month}&year=${year}&t=${Date.now()}`)
.then(r => r.json())
.then(setSlots)
}, [month, year]) // re-runs on every period change
On the API route in src/app/api/availability/route.ts, set the response header:
return new Response(JSON.stringify(slots), {
headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate' },
})
Detection
-
ID:
data-freshness -
Severity:
critical -
What to look for: Find the calendar component and trace its data loading logic. Count all calendar view components (month view, week view, day view). For each, check if there's an API call (
fetch,axios,useSWR,useQuery, or HTTP client) that fires when the calendar mounts or when the user navigates to a different month/week. Look for caching mechanisms — if the code caches availability data without a time-based expiry or cache-buster, flag it. Check if the availability endpoint has aCache-Controlheader set tono-cacheormax-age=0. Examine files matchingsrc/components/*calendar*,src/components/*Calendar*,src/app/*booking*,src/app/*calendar*,src/app/api/availability*. -
Pass criteria: Count all calendar view mount/navigation paths. 100% of calendar view loads (mount or navigation to a different time period) must trigger a fresh API call to the availability endpoint. No stale cached data may be displayed. The availability fetch must include either a cache-buster parameter (e.g.,
Date.now()) or properCache-Control: no-cacheheaders. Report: "X of Y calendar view paths fetch fresh data from the server." -
Fail criteria: Availability data is cached indefinitely on the client side, or the API is called only once per session and data is reused even after bookings may have changed.
-
Do NOT pass when: A
useEffector data-fetching hook exists but its dependency array is empty ([]) — this means it fires only on mount, not on navigation between time periods. Also do NOT pass whenstaleTime: Infinityor equivalent infinite caching is configured in React Query / SWR. -
Skip (N/A) when: Never — calendar availability must always be current.
-
Detail on fail: Example:
"Calendar mounts with a static list of slots fetched once at app init. Subsequent calendar views reuse the same data without refreshing. Booked slots remain marked available." -
Cross-reference: Check
buffer-enforcement— stale data combined with missing buffer validation causes double-bookings. -
Cross-reference: Check
live-availability-update— if availability is not refetched on view load, it is also likely stale after booking. -
Cross-reference: Check
performance-load-time— fresh fetches should not block first paint; use Suspense or skeleton. -
Remediation: Fetch availability fresh on every calendar load or view change:
import { useEffect, useState } from 'react' export function Calendar({ month, year }) { const [slots, setSlots] = useState([]) useEffect(() => { // Fetch whenever month/year changes const fetchSlots = async () => { const response = await fetch( `/api/availability?month=${month}&year=${year}&cache-bust=${Date.now()}` ) const data = await response.json() setSlots(data) } fetchSlots() }, [month, year]) return ( <div> {slots.map((slot) => ( <div key={slot.id}>{slot.time}</div> ))} </div> ) }Ensure the server endpoint has proper cache headers:
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
External references
- cwe · CWE-362 — Race Condition (TOCTOU) — stale availability data enables concurrent double-booking
- iso-25010:2011 · functional-correctness — Functional Correctness — calendar must reflect true current state
Taxons
History
- 2026-04-18·v1.0.0·Initial import from booking-calendar-availability·automated