Feed Caching & Staleness
Why it matters
A feed endpoint with no Cache-Control header or client-side staleTime triggers a full database query on every page visit, every back-navigation, and every tab focus. For a social feed, this means re-fetching 20 posts with author joins and like counts every few seconds per active user — a multiplier that kills database throughput under modest concurrent load. ISO 25010:2011 performance-efficiency requires that repeated reads of slowly-changing data not incur full round-trip costs each time. Without cache invalidation on post creation, you also risk serving stale feeds, but that is a secondary concern relative to the cost of zero caching.
Severity rationale
Low because the impact accumulates gradually with user count and does not cause correctness failures, but the fix is low-effort and the cost of omission scales linearly with engagement.
Remediation
Add a short stale-while-revalidate cache header to the feed API response, and configure staleTime on the client-side query so back-navigation reuses cached data rather than re-fetching.
// app/api/feed/route.ts
export async function GET(req: Request) {
const feed = await getFeed(getCurrentUserId())
return Response.json(feed, {
headers: {
'Cache-Control': 'private, max-age=30, stale-while-revalidate=120'
}
})
}
// hooks/useFeed.ts
export function useFeed() {
return useQuery({
queryKey: ['feed'],
queryFn: () => fetch('/api/feed').then(r => r.json()),
staleTime: 60 * 1000, // treat data as fresh for 60s
gcTime: 10 * 60 * 1000 // keep in cache for 10 min
})
}
// After creating a post, invalidate so the new item appears
queryClient.invalidateQueries({ queryKey: ['feed'] })
Detection
-
ID:
feed-caching-strategy -
Severity:
low -
What to look for: Enumerate all relevant files and Examine the feed endpoint or server action for caching headers or client-side caching strategy. Look for: (a) HTTP cache headers (
Cache-Control,ETag), (b) client-side caching (React Query/SWR with appropriate stale times), or (c) edge caching (Vercel, Cloudflare). Verify that the feed doesn't refetch entirely when navigating back to a page. -
Pass criteria: No more than 0 violations are acceptable. Feed responses include caching directives or the client-side query has
staleTimeconfigured. Feed data is reused when returning to a page (not re-fetched immediately). Cache invalidation logic exists when new posts are posted. -
Fail criteria: No caching strategy, or feeds refetch completely on every page navigation.
-
Skip (N/A) when: Feed is small and refetching is not a concern.
-
Detail on fail:
"GET /api/feed has no Cache-Control headers — feed refetches on every back navigation"or"useQuery on feed has staleTime 0; data is immediately stale" -
Remediation: Implement feed caching:
// API route with cache headers export async function GET(req: Request) { const feed = await getFeed(userId) return Response.json(feed, { headers: { 'Cache-Control': 'public, max-age=60, stale-while-revalidate=300', 'ETag': hashFeed(feed) } }) } // Client-side caching export function useFeed() { return useQuery({ queryKey: ['feed'], queryFn: fetchFeed, staleTime: 5 * 60 * 1000, // 5 minutes gcTime: 30 * 60 * 1000 // 30 minutes }) } // Invalidate on post creation export async function createPost(content: string) { const post = await fetch('/api/posts', { method: 'POST', body: JSON.stringify({ content }) }) queryClient.invalidateQueries({ queryKey: ['feed'] }) return post.json() }
External references
- iso-25010:2011 · performance-efficiency — Resource Utilisation
Taxons
History
- 2026-04-18·v1.0.0·Initial import from community-social-engagement·automated