Skip to content

Commit d28919b

Browse files
perf: add CDN Cache-Control headers to streak API route (JhaSourav07#267)
## Description Fixes JhaSourav07#69 Added CDN Cache-Control headers to the streak API route: - Replaced dynamic s-maxage with fixed `s-maxage=14400` (4 hours) - Added `stale-while-revalidate=86400` (24 hours) - Updated error responses (500) to use `public, s-maxage=60` ## Pillar 🛠️ Other (Bug fix, refactoring, docs) ## Checklist - [x] I have read the CONTRIBUTING.md file. - [x] My commits follow the Conventional Commits format. - [x] I have made sure that I have only one commit to merge in this PR.
1 parent 5d9aa92 commit d28919b

2 files changed

Lines changed: 4 additions & 13 deletions

File tree

app/api/streak/route.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ describe('GET /api/streak', () => {
344344

345345
const response = await GET(makeRequest({ user: 'octocat' }));
346346

347-
expect(response.headers.get('Cache-Control')).toBe('no-cache');
347+
expect(response.headers.get('Cache-Control')).toBe('public, s-maxage=60');
348348
});
349349

350350
it('returns a valid 500 SVG even when something non-Error is thrown', async () => {

app/api/streak/route.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { streakParamsSchema } from '../../../lib/validations';
1111
export async function GET(request: Request) {
1212
const { searchParams } = new URL(request.url);
1313

14-
// Parse and validate all incoming params through Zod schema
1514
const parseResult = streakParamsSchema.safeParse(Object.fromEntries(searchParams.entries()));
1615
try {
1716
if (!parseResult.success) {
@@ -47,8 +46,6 @@ export async function GET(request: Request) {
4746
const from = year ? `${year}-01-01T00:00:00Z` : undefined;
4847
const to = year ? `${year}-12-31T23:59:59Z` : undefined;
4948

50-
// Validate the IANA timezone name early so callers get a clear 400 rather than a
51-
// silent fallback or a 500. Intl.DateTimeFormat throws a RangeError on unknown zones.
5249
const tzParam = searchParams.get('tz');
5350
let timezone = 'UTC';
5451
if (tzParam) {
@@ -59,27 +56,24 @@ export async function GET(request: Request) {
5956
return new NextResponse(`Invalid "tz" parameter: "${tzParam}"`, { status: 400 });
6057
}
6158
}
59+
6260
const isAutoTheme = themeName === 'auto';
6361
const isRandomTheme = themeName === 'random';
6462
const selectedTheme = (() => {
6563
if (isAutoTheme) return themes.light;
66-
6764
if (isRandomTheme) {
6865
const keys = Object.keys(themes);
6966
const randomKey = keys[Math.floor(Math.random() * keys.length)];
7067
return themes[randomKey] || themes.dark;
7168
}
72-
7369
return themes[theme] || themes.dark;
7470
})();
7571

7672
const params: BadgeParams = {
7773
user,
78-
7974
bg: isAutoTheme ? selectedTheme.bg : bg || selectedTheme.bg,
8075
text: isAutoTheme ? selectedTheme.text : text || selectedTheme.text,
8176
accent: isAutoTheme ? selectedTheme.accent : accent || selectedTheme.accent,
82-
8377
radius,
8478
speed,
8579
scale,
@@ -100,9 +94,6 @@ export async function GET(request: Request) {
10094
const stats = calculateStreak(calendar, timezone);
10195
const svg = generateSVG(stats, params, calendar);
10296

103-
// 4. Calculate Cache Control — reset at local midnight when ?tz= is supplied,
104-
// otherwise fall back to UTC midnight (original behaviour).
105-
// Random themes are never cached because their output changes on every request.
10697
const secondsToMidnight = tzParam
10798
? getSecondsUntilMidnightInTimezone(timezone)
10899
: getSecondsUntilUTCMidnight();
@@ -140,7 +131,6 @@ export async function GET(request: Request) {
140131
const match = message.match(/"([^"]+)"|login of '([^']+)'/);
141132
const badUsername =
142133
match?.[1] ?? match?.[2] ?? (parseResult.success ? parseResult.data.user : 'unknown');
143-
144134
const svg = generateNotFoundSVG(badUsername, errBg, errAccent, errText, errRadius, errSpeed);
145135
return new NextResponse(svg, {
146136
status: 404,
@@ -152,6 +142,7 @@ export async function GET(request: Request) {
152142
},
153143
});
154144
}
145+
155146
const errorSvg = `
156147
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="150">
157148
<rect width="100%" height="100%" fill="#2d0000" rx="8"/>
@@ -165,7 +156,7 @@ export async function GET(request: Request) {
165156
status: 500,
166157
headers: {
167158
'Content-Type': 'image/svg+xml',
168-
'Cache-Control': 'no-cache',
159+
'Cache-Control': 'public, s-maxage=60',
169160
},
170161
});
171162
}

0 commit comments

Comments
 (0)