From 9f1d8bb58cec13f1724d6382d4cfe50c6997921d Mon Sep 17 00:00:00 2001 From: Akshay Date: Sun, 23 Nov 2025 10:02:11 +0530 Subject: [PATCH] refactor: Update caching strategy across API routes and configuration to prevent stale data, ensuring fresh responses for dynamic content. --- app/[username]/page.tsx | 5 ++ app/api/companies/[slug]/route.ts | 16 +---- app/api/companies/route.ts | 16 +---- app/api/events/[slug]/route.ts | 16 +---- app/api/events/featured/route.ts | 16 +---- app/api/events/route.ts | 16 +---- app/api/hackathons/route.ts | 28 +++----- app/api/leaderboard/route.ts | 16 ++--- next.config.ts | 112 ++++-------------------------- vercel.json | 11 +-- 10 files changed, 49 insertions(+), 203 deletions(-) diff --git a/app/[username]/page.tsx b/app/[username]/page.tsx index 0677bc206..1128de0bf 100644 --- a/app/[username]/page.tsx +++ b/app/[username]/page.tsx @@ -5,6 +5,11 @@ import { createClient } from '@/lib/supabase/server'; import Header from "@/components/header"; import Footer from "@/components/footer"; +// Force dynamic rendering to prevent stale data +export const dynamic = 'force-dynamic'; +export const revalidate = 0; +export const fetchCache = 'force-no-store'; + interface UsernamePageProps { params: Promise<{ username: string; diff --git a/app/api/companies/[slug]/route.ts b/app/api/companies/[slug]/route.ts index f1e038698..a4db82c8c 100644 --- a/app/api/companies/[slug]/route.ts +++ b/app/api/companies/[slug]/route.ts @@ -20,15 +20,7 @@ export async function GET( try { const { slug } = await params - // Try to get from cache first - const cacheKey = `company:${slug}` - const cached = await UnifiedCache.get(cacheKey) - - if (cached) { - return UnifiedCache.createResponse(cached, 'API_STANDARD') - } - - // Fetch company + // Fetch company - no caching to prevent stale data const company = await companyService.getCompanyBySlug(slug) if (!company) { @@ -110,10 +102,8 @@ export async function GET( total_participants: eventParticipants + hackathonParticipants, } - // Cache the result - await UnifiedCache.set(cacheKey, { company: enrichedCompany }, 'API_STANDARD') - - return UnifiedCache.createResponse({ company: enrichedCompany }, 'API_STANDARD') + // Return with no-cache headers to prevent stale data + return UnifiedCache.createResponse({ company: enrichedCompany }, 'USER_PRIVATE') } catch (error) { console.error('Error in GET /api/companies/[slug]:', error) diff --git a/app/api/companies/route.ts b/app/api/companies/route.ts index d5972f7b4..28b625367 100644 --- a/app/api/companies/route.ts +++ b/app/api/companies/route.ts @@ -25,21 +25,11 @@ export async function GET(request: NextRequest) { offset: parseInt(searchParams.get('offset') || '0'), } - // Try to get from cache first - const cacheKey = `companies:list:${JSON.stringify(filters)}` - const cached = await UnifiedCache.get(cacheKey) - - if (cached) { - return UnifiedCache.createResponse(cached, 'API_STANDARD') - } - - // Fetch from database + // Fetch from database - no caching to prevent stale data const result = await companyService.listCompanies(filters) - // Cache the result - await UnifiedCache.set(cacheKey, result, 'API_STANDARD') - - return UnifiedCache.createResponse(result, 'API_STANDARD') + // Return with no-cache headers to prevent stale data + return UnifiedCache.createResponse(result, 'USER_PRIVATE') } catch (error) { console.error('Error in GET /api/companies:', error) diff --git a/app/api/events/[slug]/route.ts b/app/api/events/[slug]/route.ts index 231f0a808..6690779b1 100644 --- a/app/api/events/[slug]/route.ts +++ b/app/api/events/[slug]/route.ts @@ -15,15 +15,7 @@ export async function GET( try { const { slug } = await params; - // Try to get from cache first - const cacheKey = `event:${slug}`; - const cached = await UnifiedCache.get(cacheKey); - - if (cached) { - return UnifiedCache.createResponse(cached, 'API_STANDARD'); - } - - // Fetch from database + // Fetch from database - no caching to prevent stale data const event = await eventsService.getEventBySlug(slug); if (!event) { @@ -33,10 +25,8 @@ export async function GET( ); } - // Cache the result - await UnifiedCache.set(cacheKey, event, 'API_STANDARD'); - - return UnifiedCache.createResponse(event, 'API_STANDARD'); + // Return with no-cache headers to prevent stale data + return UnifiedCache.createResponse(event, 'USER_PRIVATE'); } catch (error) { console.error('Error in GET /api/events/[slug]:', error); diff --git a/app/api/events/featured/route.ts b/app/api/events/featured/route.ts index 2fe2405e1..271a16da7 100644 --- a/app/api/events/featured/route.ts +++ b/app/api/events/featured/route.ts @@ -12,21 +12,11 @@ export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const limit = parseInt(searchParams.get('limit') || '5'); - // Try to get from cache first - const cacheKey = `featured_events:${limit}`; - const cached = await UnifiedCache.get(cacheKey); - - if (cached) { - return UnifiedCache.createResponse({ events: cached }, 'API_STANDARD'); - } - - // Fetch from database + // Fetch from database - no caching to prevent stale data const events = await eventsService.getFeaturedEvents(limit); - - // Cache the result - await UnifiedCache.set(cacheKey, events, 'API_STANDARD'); - return UnifiedCache.createResponse({ events }, 'API_STANDARD'); + // Return with no-cache headers to prevent stale data + return UnifiedCache.createResponse({ events }, 'USER_PRIVATE'); } catch (error) { console.error('Error in GET /api/events/featured:', error); diff --git a/app/api/events/route.ts b/app/api/events/route.ts index 69914ce11..0cc4336be 100644 --- a/app/api/events/route.ts +++ b/app/api/events/route.ts @@ -68,21 +68,11 @@ export async function GET(request: NextRequest) { offset: parseInt(searchParams.get('offset') || '0') }; - // Try to get from cache first - const cacheKey = `events:${JSON.stringify(filters)}`; - const cached = await UnifiedCache.get(cacheKey); - - if (cached) { - return UnifiedCache.createResponse(cached, 'API_STANDARD'); - } - - // Fetch from database + // Fetch from database - no caching to prevent stale data const result = await eventsService.getEvents(filters); - - // Cache the result - await UnifiedCache.set(cacheKey, result, 'API_STANDARD'); - return UnifiedCache.createResponse(result, 'API_STANDARD'); + // Return with no-cache headers to prevent stale data + return UnifiedCache.createResponse(result, 'USER_PRIVATE'); } catch (error) { console.error('Error in GET /api/events:', error); diff --git a/app/api/hackathons/route.ts b/app/api/hackathons/route.ts index 363c41d31..4487acd63 100644 --- a/app/api/hackathons/route.ts +++ b/app/api/hackathons/route.ts @@ -66,25 +66,17 @@ export async function GET(request: NextRequest) { offset: searchParams.get('offset') ? parseInt(searchParams.get('offset')!) : undefined, }; - // Create cache key based on filters - const cacheKey = `hackathons-${JSON.stringify(filters)}`; - - // Use unified cache system with DYNAMIC_CONTENT strategy for fast updates - const result = await UnifiedCache.cachedQuery( - cacheKey, - async () => { - // Add timeout to prevent hanging requests - const timeoutPromise = new Promise((_, reject) => { - setTimeout(() => reject(new Error('Request timeout')), 8000); - }); - - const resultPromise = hackathonsService.getHackathons(filters); - return await Promise.race([resultPromise, timeoutPromise]); - }, - 'DYNAMIC_CONTENT' // Use dynamic content strategy for hackathons - ); + // Fetch from database - no caching to prevent stale data + // Add timeout to prevent hanging requests + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('Request timeout')), 8000); + }); + + const resultPromise = hackathonsService.getHackathons(filters); + const result = await Promise.race([resultPromise, timeoutPromise]); - return UnifiedCache.createResponse(result, 'DYNAMIC_CONTENT'); + // Return with no-cache headers to prevent stale data + return UnifiedCache.createResponse(result, 'USER_PRIVATE'); } catch (error) { console.error('Error in GET /api/hackathons:', error); diff --git a/app/api/leaderboard/route.ts b/app/api/leaderboard/route.ts index e1fff4ead..bc0ce3ee5 100644 --- a/app/api/leaderboard/route.ts +++ b/app/api/leaderboard/route.ts @@ -21,19 +21,11 @@ export async function GET(request: NextRequest) { const timeRange = searchParams.get('timeRange') || 'all'; const badge = searchParams.get('badge') || null; - // Create cache key based on parameters - const cacheKey = `leaderboard-${page}-${limit}-${timeRange}-${badge || 'all'}`; - - // Use unified cache system - const data = await UnifiedCache.cachedQuery( - cacheKey, - async () => { - return await fetchLeaderboardData(page, limit, timeRange, badge); - }, - 'API_STANDARD' - ); + // Fetch from database - no caching to prevent stale data + const data = await fetchLeaderboardData(page, limit, timeRange, badge); - return UnifiedCache.createResponse(data, 'API_STANDARD'); + // Return with no-cache headers to prevent stale data + return UnifiedCache.createResponse(data, 'USER_PRIVATE'); } catch (error) { console.error('Leaderboard API error:', error); diff --git a/next.config.ts b/next.config.ts index 44008a704..d94b8b29a 100644 --- a/next.config.ts +++ b/next.config.ts @@ -131,10 +131,6 @@ const nextConfig: NextConfig = { key: 'Vary', value: 'Accept-Encoding', }, - { - key: 'Cache-Tag', - value: 'static', - }, ], }, @@ -152,86 +148,32 @@ const nextConfig: NextConfig = { key: 'CDN-Cache-Control', value: isProd ? 'public, max-age=2592000, immutable' : 'no-cache', }, - { - key: 'Cache-Tag', - value: 'media', - }, ], }, - // HOMEPAGE: No caching to ensure auth state is always fresh + // ALL DYNAMIC PAGES: No caching to prevent stale data { - source: '/', + source: '/((?!_next|api).*)', headers: [ { key: 'Cache-Control', - value: 'no-cache, no-store, must-revalidate', + value: 'no-cache, no-store, must-revalidate, max-age=0', }, { key: 'CDN-Cache-Control', - value: 'no-cache', + value: 'no-cache, no-store, must-revalidate', }, { key: 'Pragma', value: 'no-cache', }, { - key: 'Vary', - value: 'Cookie, Accept-Encoding', - }, - { - key: 'X-Build-ID', - value: buildId, - }, - ], - }, - - // DYNAMIC CONTENT: Dynamic pages (events, hackathons, etc.) - { - source: '/(hackathons|events|leaderboard|opportunities)/:path*', - headers: [ - { - key: 'Cache-Control', - value: isDev - ? 'no-cache, no-store, must-revalidate' - : 'public, max-age=60, stale-while-revalidate=300', // 1min cache, 5min SWR - }, - { - key: 'CDN-Cache-Control', - value: isProd ? 'public, max-age=60, stale-while-revalidate=300' : 'no-cache', - }, - { - key: 'X-Build-ID', - value: buildId, - }, - { - key: 'Cache-Tag', - value: 'content', + key: 'Expires', + value: '0', }, { key: 'Vary', - value: 'Cookie, Accept-Encoding', - }, - ], - }, - - // DATABASE QUERIES: API routes that query database - { - source: '/api/(hackathons|leaderboard|tests|verify-certificate)/:path*', - headers: [ - { - key: 'Cache-Control', - value: isDev - ? 'no-cache, no-store, must-revalidate' - : 'public, max-age=300, stale-while-revalidate=600', // 5min cache, 10min SWR - }, - { - key: 'CDN-Cache-Control', - value: isProd ? 'public, max-age=300, stale-while-revalidate=600' : 'no-cache', - }, - { - key: 'Cache-Tag', - value: 'api', + value: 'Cookie, Accept-Encoding, Authorization', }, { key: 'X-Build-ID', @@ -240,56 +182,30 @@ const nextConfig: NextConfig = { ], }, - // USER PRIVATE: Auth and user-specific routes + // API ROUTES: No caching for dynamic data { - source: '/(protected|admin|profile|dashboard|auth)/:path*', + source: '/api/:path*', headers: [ { key: 'Cache-Control', - value: 'private, no-cache, no-store, must-revalidate', + value: 'no-cache, no-store, must-revalidate, max-age=0', }, { key: 'CDN-Cache-Control', - value: 'no-cache', + value: 'no-cache, no-store, must-revalidate', }, { key: 'Pragma', value: 'no-cache', }, - ], - }, - - // API STANDARD: General API routes and public pages - { - source: '/((?!_next|protected|admin|profile|dashboard|auth).*)', - headers: [ - { - key: 'Cache-Control', - value: isDev - ? 'no-cache, no-store, must-revalidate' - : 'public, max-age=0, must-revalidate', // Always revalidate HTML - }, - { - key: 'CDN-Cache-Control', - value: isProd ? 'public, max-age=60, stale-while-revalidate=300' : 'no-cache', - }, { - key: 'Cache-Tag', - value: 'pages', - }, - { - key: 'X-Build-ID', - value: buildId, + key: 'Expires', + value: '0', }, { key: 'Vary', - value: 'Cookie, Accept-Encoding', + value: 'Cookie, Accept-Encoding, Authorization', }, - // Security headers - { key: 'X-Content-Type-Options', value: 'nosniff' }, - { key: 'X-Frame-Options', value: 'DENY' }, - { key: 'X-XSS-Protection', value: '1; mode=block' }, - { key: 'Referrer-Policy', value: 'origin-when-cross-origin' }, ], }, ] diff --git a/vercel.json b/vercel.json index 982c82ab3..91adda539 100644 --- a/vercel.json +++ b/vercel.json @@ -25,16 +25,7 @@ "headers": [ { "key": "Cache-Control", - "value": "public, max-age=2592000, immutable" - } - ] - }, - { - "source": "/cache-manifest.json", - "headers": [ - { - "key": "Cache-Control", - "value": "public, max-age=0, must-revalidate" + "value": "public, max-age=31536000, immutable" } ] },