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.
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.
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'] })
ID: community-social-engagement.activity-feeds.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 staleTime configured. 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()
}