diff --git a/app/api/compare/route.empty-fallback.test.ts b/app/api/compare/route.empty-fallback.test.ts index 49da0b309..fd03a7861 100644 --- a/app/api/compare/route.empty-fallback.test.ts +++ b/app/api/compare/route.empty-fallback.test.ts @@ -66,7 +66,7 @@ describe('GET /api/compare - Empty & Missing Input Fallbacks', () => { const res = await GET(makeRequest('user1=alice&user2=bob')); expect(res.status).toBe(200); expect(res.headers.get('ETag')).toBeTruthy(); - expect(res.headers.get('Cache-Control')).toBe('public, s-maxage=3600'); + expect(res.headers.get('Cache-Control')).toBe('public, s-maxage=1'); }); it('returns 200 when If-None-Match is an empty string instead of crashing', async () => { diff --git a/app/api/compare/route.test.ts b/app/api/compare/route.test.ts index f28b04fc7..2ab649cf0 100644 --- a/app/api/compare/route.test.ts +++ b/app/api/compare/route.test.ts @@ -181,7 +181,7 @@ describe('GET /api/compare', () => { const res = await GET(makeRequest('user1=alice&user2=bob')); expect(res.status).toBe(200); expect(res.headers.get('ETag')).toBeTruthy(); - expect(res.headers.get('Cache-Control')).toBe('public, s-maxage=3600'); + expect(res.headers.get('Cache-Control')).toBe('public, s-maxage=1'); }); it('returns 304 Not Modified when If-None-Match matches weak ETag', async () => { @@ -199,7 +199,7 @@ describe('GET /api/compare', () => { expect(res2.status).toBe(304); expect(res2.body).toBeNull(); expect(res2.headers.get('ETag')).toBe(etag); - expect(res2.headers.get('Cache-Control')).toBe('public, s-maxage=3600'); + expect(res2.headers.get('Cache-Control')).toBe('public, s-maxage=1'); }); it('returns 304 Not Modified when If-None-Match matches strong ETag', async () => { diff --git a/app/api/compare/route.ts b/app/api/compare/route.ts index 688867eb3..1b11c670e 100644 --- a/app/api/compare/route.ts +++ b/app/api/compare/route.ts @@ -103,7 +103,7 @@ export async function GET(request: Request) { const etag = crypto.createHash('sha1').update(jsonPayload).digest('hex'); const weakEtag = `W/"${etag}"`; const ifNoneMatch = request.headers.get('if-none-match'); - const cacheControl = 'public, s-maxage=3600'; + const cacheControl = 'public, s-maxage=1'; if (ifNoneMatch) { const etags = ifNoneMatch.split(',').map((e) => e.trim()); diff --git a/app/api/github/route.ts b/app/api/github/route.ts index 3e5ec4c6f..18208b665 100644 --- a/app/api/github/route.ts +++ b/app/api/github/route.ts @@ -172,7 +172,7 @@ export async function GET(request: Request) { const cacheControl = shouldBypassCache ? 'no-cache, no-store, must-revalidate' - : 's-maxage=3600, stale-while-revalidate=86400'; + : 's-maxage=1, stale-while-revalidate=59'; const cacheStatus = shouldBypassCache ? 'MISS' : 'HIT'; diff --git a/app/api/repo-burnout/route.ts b/app/api/repo-burnout/route.ts index 4e90fd7e6..2d0612daa 100644 --- a/app/api/repo-burnout/route.ts +++ b/app/api/repo-burnout/route.ts @@ -97,7 +97,7 @@ export async function GET(request: Request) { const cacheControl = shouldBypassCache ? 'no-cache, no-store, must-revalidate' - : 's-maxage=3600, stale-while-revalidate=86400'; + : 's-maxage=1, stale-while-revalidate=59'; return NextResponse.json(data, { status: 200, diff --git a/app/api/spotlight/route.ts b/app/api/spotlight/route.ts index 71477b4b6..cfe22ae3c 100644 --- a/app/api/spotlight/route.ts +++ b/app/api/spotlight/route.ts @@ -104,7 +104,7 @@ export async function GET(request: Request) { return new NextResponse(null, { status: 304, headers: { - 'Cache-Control': 'public, max-age=60, s-maxage=3600, stale-while-revalidate=60', + 'Cache-Control': 'public, max-age=60, s-maxage=1, stale-while-revalidate=59', ETag: weakEtag, }, }); @@ -113,7 +113,7 @@ export async function GET(request: Request) { return new NextResponse(svg, { headers: { 'Content-Type': 'image/svg+xml; charset=utf-8', - 'Cache-Control': 'public, max-age=60, s-maxage=3600, stale-while-revalidate=60', + 'Cache-Control': 'public, max-age=60, s-maxage=1, stale-while-revalidate=59', 'Content-Security-Policy': SVG_CSP_HEADER, ETag: weakEtag, }, diff --git a/app/api/stats/route.test.ts b/app/api/stats/route.test.ts index d700e5b3c..36341e177 100644 --- a/app/api/stats/route.test.ts +++ b/app/api/stats/route.test.ts @@ -141,7 +141,7 @@ describe('GET /api/stats', () => { expect(response.status).toBe(200); expect(response.headers.get('Cache-Control')).toBe( - 'public, s-maxage=3600, stale-while-revalidate=86400' + 'public, s-maxage=1, stale-while-revalidate=59' ); expect(response.headers.get('Pragma')).toBeNull(); expect(response.headers.get('Expires')).toBeNull(); @@ -168,7 +168,7 @@ describe('GET /api/stats', () => { }); expect(response.headers.get('X-Refresh-Status')).toBe('Cooldown-Served-Cached'); expect(response.headers.get('Cache-Control')).toBe( - 'public, s-maxage=3600, stale-while-revalidate=86400' + 'public, s-maxage=1, stale-while-revalidate=59' ); }); diff --git a/app/api/stats/route.ts b/app/api/stats/route.ts index 0256fa71d..cdad77af7 100644 --- a/app/api/stats/route.ts +++ b/app/api/stats/route.ts @@ -134,7 +134,7 @@ export async function GET(request: Request) { const stats = calculateStreak(calendar, timezone); const headers = new Headers({ // Cache until next UTC midnight; clients can bust with ?refresh=true - 'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400', + 'Cache-Control': 'public, s-maxage=1, stale-while-revalidate=59', }); if (shouldBypassCache) { diff --git a/app/api/stats/route.validation.test.ts b/app/api/stats/route.validation.test.ts index 9b3ca047e..5a03c9e0a 100644 --- a/app/api/stats/route.validation.test.ts +++ b/app/api/stats/route.validation.test.ts @@ -113,7 +113,7 @@ describe('GET /api/stats additional runtime coverage', () => { expect(response.status).toBe(200); expect(response.headers.get('Cache-Control')).toBe( - 'public, s-maxage=3600, stale-while-revalidate=86400' + 'public, s-maxage=1, stale-while-revalidate=59' ); expect(response.headers.get('Pragma')).toBeNull(); expect(response.headers.get('Expires')).toBeNull(); diff --git a/app/api/streak/route.test.ts b/app/api/streak/route.test.ts index 8764ee037..8fa563a04 100644 --- a/app/api/streak/route.test.ts +++ b/app/api/streak/route.test.ts @@ -443,7 +443,7 @@ describe('GET /api/streak', () => { it('caches until UTC midnight by default, using the value from getSecondsUntilUTCMidnight', async () => { const response = await GET(makeRequest({ user: 'octocat' })); expect(response.headers.get('Cache-Control')).toBe( - 'public, max-age=60, s-maxage=3600, stale-while-revalidate=60' + 'public, max-age=60, s-maxage=1, stale-while-revalidate=59' ); }); @@ -451,7 +451,7 @@ describe('GET /api/streak', () => { vi.mocked(getSecondsUntilUTCMidnight).mockReturnValue(7200); const response = await GET(makeRequest({ user: 'octocat' })); expect(response.headers.get('Cache-Control')).toBe( - 'public, max-age=60, s-maxage=7200, stale-while-revalidate=60' + 'public, max-age=60, s-maxage=1, stale-while-revalidate=59' ); }); @@ -1013,7 +1013,7 @@ describe('GET /api/streak', () => { const response = await GET(makeRequest({ user: 'octocat', tz: 'America/New_York' })); expect(response.headers.get('Cache-Control')).toBe( - 'public, max-age=60, s-maxage=7200, stale-while-revalidate=60' + 'public, max-age=60, s-maxage=1, stale-while-revalidate=59' ); expect(getSecondsUntilMidnightInTimezone).toHaveBeenCalledWith('America/New_York'); expect(getSecondsUntilUTCMidnight).not.toHaveBeenCalled(); @@ -1571,16 +1571,16 @@ describe('GET /api/streak', () => { }); describe('stale-while-revalidate cache header', () => { - it('contains stale-while-revalidate=60 for normal request', async () => { + it('contains stale-while-revalidate=59 for normal request', async () => { const response = await GET(makeRequest({ user: 'octocat' })); - expect(response.headers.get('Cache-Control')).toContain('stale-while-revalidate=60'); + expect(response.headers.get('Cache-Control')).toContain('stale-while-revalidate=59'); }); it('does NOT contain stale-while-revalidate when ?refresh=true', async () => { const response = await GET(makeRequest({ user: 'octocat', refresh: 'true' })); - expect(response.headers.get('Cache-Control')).not.toContain('stale-while-revalidate=60'); + expect(response.headers.get('Cache-Control')).not.toContain('stale-while-revalidate=59'); }); }); diff --git a/app/api/streak/route.ts b/app/api/streak/route.ts index 5586ea02b..e81f391c9 100644 --- a/app/api/streak/route.ts +++ b/app/api/streak/route.ts @@ -551,7 +551,7 @@ export async function GET(request: Request) { : getSecondsUntilUTCMidnight(); const cacheControl = isRefreshRequested ? 'no-cache, no-store, must-revalidate' - : `public, s-maxage=${secondsToMidnight}, stale-while-revalidate=86400`; + : `public, s-maxage=1, stale-while-revalidate=86400`; const cacheStatusHeader = shouldBypassCache ? `BYPASS, fetched=${new Date().toISOString()}` @@ -662,7 +662,7 @@ export async function GET(request: Request) { ? 'no-cache, no-store, must-revalidate' : isHistoricalYear ? 'public, max-age=31536000, s-maxage=31536000, immutable' - : `public, max-age=60, s-maxage=${secondsToMidnight}, stale-while-revalidate=60`; + : `public, max-age=60, s-maxage=1, stale-while-revalidate=59`; const etag = crypto.createHash('sha256').update(svg).digest('hex'); const weakEtag = `W/"${etag}"`; diff --git a/app/api/streak/tests/dateRange.test.ts b/app/api/streak/tests/dateRange.test.ts index 9bb4555b3..dd8227259 100644 --- a/app/api/streak/tests/dateRange.test.ts +++ b/app/api/streak/tests/dateRange.test.ts @@ -118,7 +118,7 @@ describe('GET /api/streak dateRange parameter', () => { expect(res.status).toBe(200); expect(res.headers.get('Content-Type')).toBe('image/svg+xml; charset=utf-8'); expect(res.headers.get('Cache-Control')).toBe( - 'public, max-age=60, s-maxage=3600, stale-while-revalidate=60' + 'public, max-age=60, s-maxage=1, stale-while-revalidate=59' ); }); }); diff --git a/app/api/streak/tests/refresh.test.ts b/app/api/streak/tests/refresh.test.ts index cac9eb662..94341cd63 100644 --- a/app/api/streak/tests/refresh.test.ts +++ b/app/api/streak/tests/refresh.test.ts @@ -105,7 +105,7 @@ describe('GET /api/streak - refresh parameter group', () => { expect(response.status).toBe(200); expect(response.headers.get('Cache-Control')).toBe( - 'public, max-age=60, s-maxage=3600, stale-while-revalidate=60' + 'public, max-age=60, s-maxage=1, stale-while-revalidate=59' ); expect(response.headers.get('X-Cache-Status')).toBe('HIT'); }); diff --git a/app/api/streak/tests/views.test.ts b/app/api/streak/tests/views.test.ts index b0564f68c..2bc42e5cc 100644 --- a/app/api/streak/tests/views.test.ts +++ b/app/api/streak/tests/views.test.ts @@ -159,7 +159,7 @@ describe('GET /api/streak view parameter integration', () => { expect(response.status).toBe(200); expect(response.headers.get('Content-Type')).toBe('image/svg+xml; charset=utf-8'); expect(response.headers.get('Cache-Control')).toBe( - 'public, max-age=60, s-maxage=3600, stale-while-revalidate=60' + 'public, max-age=60, s-maxage=1, stale-while-revalidate=59' ); expect(response.headers.get('X-Cache-Status')).toBe('HIT'); }); diff --git a/app/api/streak/tests/weekday.test.ts b/app/api/streak/tests/weekday.test.ts index f1e6f82f6..04f0f70e3 100644 --- a/app/api/streak/tests/weekday.test.ts +++ b/app/api/streak/tests/weekday.test.ts @@ -131,7 +131,7 @@ describe('GET /api/streak?view=weekday', () => { expect(response.status).toBe(200); expect(response.headers.get('Cache-Control')).toBe( - 'public, max-age=60, s-maxage=3600, stale-while-revalidate=60' + 'public, max-age=60, s-maxage=1, stale-while-revalidate=59' ); expect(response.headers.get('X-Cache-Status')).toBe('HIT'); }); diff --git a/utils/cacheControl.empty-fallback.test.ts b/utils/cacheControl.empty-fallback.test.ts index b8d763102..6fc280f8a 100644 --- a/utils/cacheControl.empty-fallback.test.ts +++ b/utils/cacheControl.empty-fallback.test.ts @@ -5,17 +5,17 @@ import { buildCacheControlHeader } from './cacheControl'; describe('buildCacheControlHeader - Edge Cases & Empty/Missing Inputs Verification', () => { it('1. falls back to default cache control when options object is completely omitted', () => { // Calling the function with no arguments should fallback gracefully without throwing - expect(buildCacheControlHeader()).toBe('s-maxage=3600, stale-while-revalidate=86400'); + expect(buildCacheControlHeader()).toBe('public, s-maxage=1, stale-while-revalidate=59'); }); it('2. falls back to default cache control when options is an empty object', () => { - expect(buildCacheControlHeader({})).toBe('s-maxage=3600, stale-while-revalidate=86400'); + expect(buildCacheControlHeader({})).toBe('public, s-maxage=1, stale-while-revalidate=59'); }); it('3. falls back to default cache control when options object is null', () => { // Casting to any to test JavaScript-level null input resilience expect(buildCacheControlHeader(null as any)).toBe( - 's-maxage=3600, stale-while-revalidate=86400' + 'public, s-maxage=1, stale-while-revalidate=59' ); }); @@ -26,7 +26,7 @@ describe('buildCacheControlHeader - Edge Cases & Empty/Missing Inputs Verificati secondsToMidnight: undefined, isHistoricalYear: undefined, }) - ).toBe('s-maxage=3600, stale-while-revalidate=86400'); + ).toBe('public, s-maxage=1, stale-while-revalidate=59'); expect( buildCacheControlHeader({ @@ -34,7 +34,7 @@ describe('buildCacheControlHeader - Edge Cases & Empty/Missing Inputs Verificati secondsToMidnight: null as any, isHistoricalYear: null as any, }) - ).toBe('s-maxage=3600, stale-while-revalidate=86400'); + ).toBe('public, s-maxage=1, stale-while-revalidate=59'); }); it('5. prioritizes bypass=true even if other options are invalid/null/undefined', () => { diff --git a/utils/cacheControl.test.ts b/utils/cacheControl.test.ts index bc6638e30..fdafbece0 100644 --- a/utils/cacheControl.test.ts +++ b/utils/cacheControl.test.ts @@ -14,11 +14,11 @@ describe('buildCacheControlHeader', () => { it('returns s-maxage with secondsToMidnight', () => { expect(buildCacheControlHeader({ secondsToMidnight: 3600 })).toBe( - 'public, s-maxage=3600, stale-while-revalidate=86400' + 'public, s-maxage=1, stale-while-revalidate=59' ); }); it('returns default s-maxage when no options given', () => { - expect(buildCacheControlHeader({})).toBe('s-maxage=3600, stale-while-revalidate=86400'); + expect(buildCacheControlHeader({})).toBe('public, s-maxage=1, stale-while-revalidate=59'); }); }); diff --git a/utils/cacheControl.theme-contrast.test.ts b/utils/cacheControl.theme-contrast.test.ts index 5c81c560c..920305986 100644 --- a/utils/cacheControl.theme-contrast.test.ts +++ b/utils/cacheControl.theme-contrast.test.ts @@ -4,7 +4,7 @@ import { buildCacheControlHeader } from './cacheControl'; describe('cacheControl - Dark and Light Prefers-Color-Scheme Visual Cohesion', () => { it('returns the default s-maxage header when no options are provided', () => { const header = buildCacheControlHeader({}); - expect(header).toBe('s-maxage=3600, stale-while-revalidate=86400'); + expect(header).toBe('public, s-maxage=1, stale-while-revalidate=59'); }); it('returns a no-store directive when bypass is true, overriding all other options', () => { @@ -23,12 +23,11 @@ describe('cacheControl - Dark and Light Prefers-Color-Scheme Visual Cohesion', ( it('includes secondsToMidnight in the s-maxage when provided without bypass or historical', () => { const header = buildCacheControlHeader({ secondsToMidnight: 7200 }); - expect(header).toMatch(/^public, s-maxage=7200,/); - expect(header).toContain('stale-while-revalidate=86400'); + expect(header).toBe('public, s-maxage=1, stale-while-revalidate=59'); }); it('handles secondsToMidnight of zero correctly, producing s-maxage=0', () => { const header = buildCacheControlHeader({ secondsToMidnight: 0 }); - expect(header).toBe('public, s-maxage=0, stale-while-revalidate=86400'); + expect(header).toBe('public, s-maxage=1, stale-while-revalidate=59'); }); }); diff --git a/utils/cacheControl.ts b/utils/cacheControl.ts index f747b951e..5dc123a9b 100644 --- a/utils/cacheControl.ts +++ b/utils/cacheControl.ts @@ -22,8 +22,8 @@ export function buildCacheControlHeader(options: CacheControlOptions = {}): stri } if (secondsToMidnight !== undefined && secondsToMidnight !== null) { - return `public, s-maxage=${secondsToMidnight}, stale-while-revalidate=86400`; + return `public, s-maxage=1, stale-while-revalidate=59`; } - return 's-maxage=3600, stale-while-revalidate=86400'; + return 'public, s-maxage=1, stale-while-revalidate=59'; }