Skip to content

perf: add CDN cache headers to public getServerSideProps pages#1346

Open
devanshk404 wants to merge 3 commits into
SuperteamDAO:mainfrom
devanshk404:perf/cdn-cache-headers
Open

perf: add CDN cache headers to public getServerSideProps pages#1346
devanshk404 wants to merge 3 commits into
SuperteamDAO:mainfrom
devanshk404:perf/cdn-cache-headers

Conversation

@devanshk404

@devanshk404 devanshk404 commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add s-maxage=60, stale-while-revalidate=600 to 19 public data pages (listings, grants, hackathons, regions, skills, search, etc.)
  • Add s-maxage=3600, stale-while-revalidate=86400 to 9 redirect-only pages
  • Pages serving user-specific data (leaderboard, dashboard, onboarding, referral) are intentionally left uncached

Problem

All getServerSideProps pages defaulted to Cache-Control: private, no-store, causing every request — including Googlebot — to hit the origin server. This results in constant X-Vercel-Cache: MISS and unnecessary DB queries for pages that serve identical public data to all users.

Test plan

  • Deploy to preview and verify X-Vercel-Cache: HIT on second request for affected pages (e.g. /earn/listing/[slug], /earn/grants/[slug])
  • Confirm user-specific pages (leaderboard, dashboard) still return X-Vercel-Cache: MISS
  • Check that listing/grant detail pages reflect updates within ~60 seconds

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Optimized server-side response caching across earn opportunity pages, projects, and home page.
    • Implemented HTTP cache directives to improve page load performance and server efficiency.

Add s-maxage=60, stale-while-revalidate=600 to public data pages
(listings, grants, hackathons, regions, skills, etc.) and
s-maxage=3600, stale-while-revalidate=86400 to redirect-only pages.

Pages that serve user-specific data (leaderboard, dashboard,
onboarding) are intentionally left uncached.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Mar 18, 2026

Copy link
Copy Markdown

@devanshk404 is attempting to deploy a commit to the Superteam Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

PR adds server-side Cache-Control headers to 20 Next.js page files' getServerSideProps functions, enabling CDN and browser caching with policies of 60 seconds (stale-while-revalidate: 600 seconds) for most pages and 3600 seconds for project/grant listings. Includes TypeScript configuration suppression for a logging option.

Changes

Server-side caching policy rollout

Layer / File(s) Summary
Core platform pages with unconditional caching
src/pages/index.tsx, src/pages/projects.tsx, src/pages/member-perks.tsx
Homepage, projects, and member perks pages accept res from context and set Cache-Control: s-maxage=60, stale-while-revalidate=600 on success paths. Projects and member-perks also set Cache-Control: no-store on error paths before returning fallback props.
Earn opportunity and template pages
src/pages/earn/hackathon/talent-olympics.tsx, src/pages/earn/templates/listings/[slug]/index.tsx
Hackathon talent-olympics and listing template pages update getServerSideProps to accept res and unconditionally set Cache-Control: s-maxage=60, stale-while-revalidate=600 before returning data.
Conditional caching for data-dependent pages
src/pages/earn/listing/[slug]/winner.tsx, src/pages/earn/templates/listings/[slug]/index.tsx
Winner and template listing pages conditionally set cache headers only when bountyData?.id or bountyData exists, avoiding cache for missing data responses.
Dynamic region and skill pages
src/pages/earn/regions/[slug]/all.tsx, src/pages/earn/regions/[slug]/index.tsx, src/pages/earn/listing/[slug]/index.tsx, src/pages/earn/opportunities/[[...tags]].tsx, src/pages/earn/search/index.tsx, src/pages/earn/skill/[slug].tsx
Dynamic listing pages set cache headers in state-specific branches (e.g., when st or country data is resolved) or unconditionally after tag validation. Projects/all.tsx and grants redirect use 3600s/86400s policy.
Multi-path cache handling for submissions
src/pages/earn/listing/[slug]/submission/index.tsx
Submission page sets s-maxage=60 when bounty is absent or present, but switches to no-store in the error path, ensuring errors are not cached.
Configuration TypeScript suppression
next.config.ts
Adds @ts-ignore comment before logging configuration option to suppress TypeScript error for missing type definition.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • SuperteamDAO/earn#998: Both PRs modify getServerSideProps in src/pages/index.tsx—PR #998 adds parent region fetching while this PR adds cache-control headers to the same handler.
  • SuperteamDAO/earn#1123: Both PRs update src/pages/index.tsx's getServerSideProps—PR #1123 adds totalUsers data fetching while this PR adds cache-control headers.

Suggested reviewers

  • scutuatua-crypto
  • a20hek

Poem

🐰 Cache headers bloom across the pages bright,
Server-side caching keeps responses light,
Sixty seconds fresh, then gracefully stale,
CDNs speed up every earning tale!
No-store on errors—the rabbit hops in delight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'perf: add CDN cache headers to public getServerSideProps pages' clearly and accurately describes the main objective of the changeset—adding caching headers across 28 pages to improve performance.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/pages/earn/t/[slug]/edit.tsx (1)

759-777: ⚠️ Potential issue | 🟠 Major

User-specific edit page should not be cached at CDN level.

This is a profile edit page that handles user-specific data. While the SSR props only contain public data (chapter names), the page's purpose is user-specific profile editing. The authentication check occurs client-side (lines 319-327), meaning cached responses could be served before the auth redirect happens.

Per the PR objectives, "Pages serving user-specific data remain uncached." Consider removing the cache header from this page to maintain consistency with that principle.

Proposed fix
 export const getServerSideProps: GetServerSideProps = async (context) => {
-  const { res } = context;
   const { slug } = context.query;
   const chapters = await prisma.chapter.findMany({
     select: {
       name: true,
     },
     orderBy: {
       name: 'asc',
     },
   });

-  res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
   return {
     props: {
       slug,
       chapterNames: chapters.map((chapter) => chapter.name),
     },
   };
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/earn/t/`[slug]/edit.tsx around lines 759 - 777, The page's
getServerSideProps is setting a CDN cache header which can cause user-specific
edit pages to be served from cache before client-side auth runs; remove the
res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600') call
(or guard it so it is never set for this user-specific edit route) inside
getServerSideProps to ensure responses are not cached at the CDN edge; update
the getServerSideProps function accordingly (refer to the getServerSideProps
export, the context/res usage, and the existing chapterNames response).
src/pages/earn/listing/[slug]/index.tsx (1)

107-131: ⚠️ Potential issue | 🟠 Major

Gate shared caching to successful data fetches.

Line 131 currently caches responses even when the fetch failed and Line 109 set listingData = null. That risks serving cached degraded content across users.

Suggested fix
-  res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
+  if (listingData?.id) {
+    res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/earn/listing/`[slug]/index.tsx around lines 107 - 131, The response
is being cached unconditionally even when fetching failed (listingData is null);
update the logic so Cache-Control is only set for successful fetches—e.g., move
or wrap the res.setHeader('Cache-Control', 's-maxage=60,
stale-while-revalidate=600') inside the block that runs when listingData?.id is
truthy (the same block that constructs QueryClient and prefetches via
submissionCountQuery and listingWinnersQuery), otherwise leave caching off or
set a conservative no-cache header; ensure you reference listingData,
res.setHeader, QueryClient, prefetchQuery, submissionCountQuery, and
listingWinnersQuery when making the change.
src/pages/earn/listing/[slug]/winner.tsx (1)

132-137: ⚠️ Potential issue | 🟠 Major

Avoid caching error fallback responses.

Line 137 sets shared caching even when Line 132-135 has already fallen back to bountyData = null after an exception. That can cache a degraded response for all users for at least 60s.

Suggested fix
-  res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
+  if (bountyData?.id) {
+    res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/earn/listing/`[slug]/winner.tsx around lines 132 - 137, The current
try/catch falls back to bountyData = null on error but unconditionally sets a
shared Cache-Control header via res.setHeader, potentially caching degraded
responses; update the logic around the try/catch and res.setHeader so that
successful data responses get the s-maxage/stale-while-revalidate header (e.g.
move or apply res.setHeader inside the success path or when bountyData !== null)
and ensure the error path explicitly sets a non-cache or no-store header (or
omits s-maxage) so the fallback (bountyData === null) is not cached; locate the
try/catch that assigns bountyData and the res.setHeader call to implement this
conditional header behavior.
src/pages/member-perks.tsx (1)

154-160: ⚠️ Potential issue | 🟠 Major

Avoid caching empty fallback content on fetch failures.

Line 154 applies the same cache policy to the error path that returns empty arrays. A transient Airtable failure can be cached and served as empty content. Prefer no-store (or a much shorter TTL) for this fallback response.

Suggested fix
-    res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
+    res.setHeader('Cache-Control', 'no-store');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/member-perks.tsx` around lines 154 - 160, The error path currently
sets res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600')
and returns empty arrays for liveNow/completed/comingSoon, which risks caching
transient failures; change the error/fallback branch so it sets a non-cacheable
header (e.g., res.setHeader('Cache-Control', 'no-store') or a very short TTL)
before returning the fallback props object (the return that includes liveNow:
[], completed: [], comingSoon: []), ensuring only successful fetches use the
original s-maxage/stale-while-revalidate policy.
src/pages/projects.tsx (1)

88-92: ⚠️ Potential issue | 🟠 Major

Do not cache error fallback payloads with the same TTL as healthy data.

Line 88 caches the fallback projects: [] response. If Airtable fails briefly, users can receive cached empty results. Use no-store (or a very short TTL) in the catch path.

Suggested fix
-    res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
+    res.setHeader('Cache-Control', 'no-store');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/projects.tsx` around lines 88 - 92, The catch path currently sets
the same Cache-Control as successful responses and returns an empty projects
array; change the error handling so that when returning the fallback (projects:
[] in the catch) you set a non-cacheable header (e.g., use
res.setHeader('Cache-Control', 'no-store') or a very short TTL) instead of
reusing res.setHeader('Cache-Control', 's-maxage=60,
stale-while-revalidate=600'), and ensure the fallback return still contains
props: { projects: [] } so callers using getServerSideProps/getStaticProps
(where res.setHeader is called) receive the non-cached error payload.
🧹 Nitpick comments (2)
src/pages/earn/listings/[type]/[slug]/index.tsx (1)

11-11: Consider centralizing cache profiles to avoid drift.

The directive at Line 11 is correct, but these literals are repeated across many pages. A shared cache-profile constant/helper would reduce config drift between redirect pages and content pages.

Refactor sketch
+// e.g. src/constants/cache-control.ts
+export const CACHE_CONTROL = {
+  PUBLIC_PAGE: 's-maxage=60, stale-while-revalidate=600',
+  REDIRECT: 's-maxage=3600, stale-while-revalidate=86400',
+} as const;
+import { CACHE_CONTROL } from '@/constants/cache-control';
...
-  res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=86400');
+  res.setHeader('Cache-Control', CACHE_CONTROL.REDIRECT);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/earn/listings/`[type]/[slug]/index.tsx at line 11, Centralize the
repeated cache header literal by creating a shared constant or helper (e.g.,
CACHE_PROFILE or getCacheHeader()) that holds 's-maxage=3600,
stale-while-revalidate=86400' and update all places that call res.setHeader(...)
(including the occurrence using res.setHeader('Cache-Control', 's-maxage=3600,
stale-while-revalidate=86400')) to use that constant/helper instead of the raw
string so redirect pages and content pages share the same profile and avoid
drift.
src/pages/earn/listing/[slug]/submission/index.tsx (1)

76-86: Consider whether caching error responses is intentional.

Setting cache headers in the catch block means that if getSubmissionsData throws an error, the empty response (bounty: null, submission: []) will be cached for up to 60 seconds. This could be intentional to prevent thundering herd during outages, but it also means transient errors won't recover quickly.

If this is intentional for resilience, consider adding a comment. If not, you may want to either skip setting cache headers on errors or use a shorter s-maxage for error cases.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/earn/listing/`[slug]/submission/index.tsx around lines 76 - 86, The
catch block that handles failures from getSubmissionsData currently sets a
long-lived cache header via res.setHeader('Cache-Control', 's-maxage=60,
stale-while-revalidate=600'), which will cause the error response (bounty: null,
submission: []) to be cached; either remove or change that header in the catch
branch so transient errors aren't cached (e.g., omit res.setHeader or set a very
short s-maxage for errors), or explicitly document intent with a comment above
the catch explaining why error responses should be cached; update the catch
handling in the getServerSideProps (or the function wrapping getSubmissionsData)
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pages/earn/templates/listings/`[slug]/index.tsx:
- Line 48: The response is enabling shared CDN caching unconditionally even when
bountyData is null; change the logic in the handler that sets res.setHeader so
that when bountyData is successfully fetched (bountyData !== null) you set
'Cache-Control' to 's-maxage=60, stale-while-revalidate=600', and when the fetch
failed (bountyData === null) set a conservative header like 'private, no-store'
and return an appropriate error status (e.g., res.status(500) or 404) instead of
allowing the degraded payload to be cached; update the code around the
bountyData fetch and the res.setHeader call in this file (index.tsx) to gate
s-maxage on successful fetches.

---

Outside diff comments:
In `@src/pages/earn/listing/`[slug]/index.tsx:
- Around line 107-131: The response is being cached unconditionally even when
fetching failed (listingData is null); update the logic so Cache-Control is only
set for successful fetches—e.g., move or wrap the res.setHeader('Cache-Control',
's-maxage=60, stale-while-revalidate=600') inside the block that runs when
listingData?.id is truthy (the same block that constructs QueryClient and
prefetches via submissionCountQuery and listingWinnersQuery), otherwise leave
caching off or set a conservative no-cache header; ensure you reference
listingData, res.setHeader, QueryClient, prefetchQuery, submissionCountQuery,
and listingWinnersQuery when making the change.

In `@src/pages/earn/listing/`[slug]/winner.tsx:
- Around line 132-137: The current try/catch falls back to bountyData = null on
error but unconditionally sets a shared Cache-Control header via res.setHeader,
potentially caching degraded responses; update the logic around the try/catch
and res.setHeader so that successful data responses get the
s-maxage/stale-while-revalidate header (e.g. move or apply res.setHeader inside
the success path or when bountyData !== null) and ensure the error path
explicitly sets a non-cache or no-store header (or omits s-maxage) so the
fallback (bountyData === null) is not cached; locate the try/catch that assigns
bountyData and the res.setHeader call to implement this conditional header
behavior.

In `@src/pages/earn/t/`[slug]/edit.tsx:
- Around line 759-777: The page's getServerSideProps is setting a CDN cache
header which can cause user-specific edit pages to be served from cache before
client-side auth runs; remove the res.setHeader('Cache-Control', 's-maxage=60,
stale-while-revalidate=600') call (or guard it so it is never set for this
user-specific edit route) inside getServerSideProps to ensure responses are not
cached at the CDN edge; update the getServerSideProps function accordingly
(refer to the getServerSideProps export, the context/res usage, and the existing
chapterNames response).

In `@src/pages/member-perks.tsx`:
- Around line 154-160: The error path currently sets
res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600') and
returns empty arrays for liveNow/completed/comingSoon, which risks caching
transient failures; change the error/fallback branch so it sets a non-cacheable
header (e.g., res.setHeader('Cache-Control', 'no-store') or a very short TTL)
before returning the fallback props object (the return that includes liveNow:
[], completed: [], comingSoon: []), ensuring only successful fetches use the
original s-maxage/stale-while-revalidate policy.

In `@src/pages/projects.tsx`:
- Around line 88-92: The catch path currently sets the same Cache-Control as
successful responses and returns an empty projects array; change the error
handling so that when returning the fallback (projects: [] in the catch) you set
a non-cacheable header (e.g., use res.setHeader('Cache-Control', 'no-store') or
a very short TTL) instead of reusing res.setHeader('Cache-Control',
's-maxage=60, stale-while-revalidate=600'), and ensure the fallback return still
contains props: { projects: [] } so callers using
getServerSideProps/getStaticProps (where res.setHeader is called) receive the
non-cached error payload.

---

Nitpick comments:
In `@src/pages/earn/listing/`[slug]/submission/index.tsx:
- Around line 76-86: The catch block that handles failures from
getSubmissionsData currently sets a long-lived cache header via
res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600'), which
will cause the error response (bounty: null, submission: []) to be cached;
either remove or change that header in the catch branch so transient errors
aren't cached (e.g., omit res.setHeader or set a very short s-maxage for
errors), or explicitly document intent with a comment above the catch explaining
why error responses should be cached; update the catch handling in the
getServerSideProps (or the function wrapping getSubmissionsData) accordingly.

In `@src/pages/earn/listings/`[type]/[slug]/index.tsx:
- Line 11: Centralize the repeated cache header literal by creating a shared
constant or helper (e.g., CACHE_PROFILE or getCacheHeader()) that holds
's-maxage=3600, stale-while-revalidate=86400' and update all places that call
res.setHeader(...) (including the occurrence using
res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=86400'))
to use that constant/helper instead of the raw string so redirect pages and
content pages share the same profile and avoid drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9af0aaff-1574-440a-b524-8b63e43afaf6

📥 Commits

Reviewing files that changed from the base of the PR and between 066cb31 and c4d3053.

📒 Files selected for processing (28)
  • src/pages/earn/grants/[slug]/index.tsx
  • src/pages/earn/grants/[slug]/references.tsx
  • src/pages/earn/hackathon/breakout.tsx
  • src/pages/earn/hackathon/mobius.tsx
  • src/pages/earn/hackathon/redacted.tsx
  • src/pages/earn/hackathon/talent-olympics.tsx
  • src/pages/earn/listing/[slug]/index.tsx
  • src/pages/earn/listing/[slug]/submission/index.tsx
  • src/pages/earn/listing/[slug]/winner.tsx
  • src/pages/earn/listings/[type]/[slug]/index.tsx
  • src/pages/earn/listings/[type]/[slug]/submission/[subid].tsx
  • src/pages/earn/listings/[type]/[slug]/submission/index.tsx
  • src/pages/earn/listings/[type]/[slug]/winner.tsx
  • src/pages/earn/listings/bounties/[slug]/index.tsx
  • src/pages/earn/listings/bounties/[slug]/submission.tsx
  • src/pages/earn/listings/grants/[slug].tsx
  • src/pages/earn/opportunities/[[...tags]].tsx
  • src/pages/earn/projects/all.tsx
  • src/pages/earn/regions/[slug]/all.tsx
  • src/pages/earn/regions/[slug]/index.tsx
  • src/pages/earn/search/index.tsx
  • src/pages/earn/skill/[slug].tsx
  • src/pages/earn/t/[slug]/edit.tsx
  • src/pages/earn/talent-olympics.tsx
  • src/pages/earn/templates/listings/[slug]/index.tsx
  • src/pages/index.tsx
  • src/pages/member-perks.tsx
  • src/pages/projects.tsx

bountyData = null;
}

res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid caching fallback/error payloads in shared cache.

bountyData is set to null on any fetch exception, but Line 48 still enables shared CDN caching. That can cache a degraded response for all users. Gate s-maxage behind successful fetches; keep failures as private, no-store (and optionally set an error status).

Proposed fix
 export const getServerSideProps: GetServerSideProps = async (context) => {
   const { res } = context;
   const { slug } = context.query;

   let bountyData;
+  let shouldCache = true;
   try {
     const bountyDetails = await api.get(
       `${getURL()}api/sponsor-dashboard/templates/${slug}/`,
     );
     bountyData = bountyDetails.data;
   } catch (e) {
     console.error(JSON.stringify(e, null, 2));
     bountyData = null;
+    shouldCache = false;
   }

-  res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
+  if (shouldCache) {
+    res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
+  } else {
+    res.setHeader('Cache-Control', 'private, no-store');
+  }
   return {
     props: {
       bounty: bountyData,
     },
   };
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
export const getServerSideProps: GetServerSideProps = async (context) => {
const { res } = context;
const { slug } = context.query;
let bountyData;
let shouldCache = true;
try {
const bountyDetails = await api.get(
`${getURL()}api/sponsor-dashboard/templates/${slug}/`,
);
bountyData = bountyDetails.data;
} catch (e) {
console.error(JSON.stringify(e, null, 2));
bountyData = null;
shouldCache = false;
}
if (shouldCache) {
res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
} else {
res.setHeader('Cache-Control', 'private, no-store');
}
return {
props: {
bounty: bountyData,
},
};
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/earn/templates/listings/`[slug]/index.tsx at line 48, The response
is enabling shared CDN caching unconditionally even when bountyData is null;
change the logic in the handler that sets res.setHeader so that when bountyData
is successfully fetched (bountyData !== null) you set 'Cache-Control' to
's-maxage=60, stale-while-revalidate=600', and when the fetch failed (bountyData
=== null) set a conservative header like 'private, no-store' and return an
appropriate error status (e.g., res.status(500) or 404) instead of allowing the
degraded payload to be cached; update the code around the bountyData fetch and
the res.setHeader call in this file (index.tsx) to gate s-maxage on successful
fetches.

@vercel

vercel Bot commented Mar 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
earn Ready Ready Preview May 6, 2026 5:25am

Request Review

Comment thread src/pages/earn/t/[slug]/edit.tsx Outdated
Comment on lines +760 to +772
@@ -768,6 +769,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
},
});

res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove from here, not a public facing page

Comment on lines 7 to 12
export const getServerSideProps: GetServerSideProps = async (context) => {
const { res } = context;
const { slug } = context.params as { slug: string };

res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=86400');
return {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a redirect no need for this

Comment on lines +8 to +11
const { res } = context;
const { slug } = context.params as { slug: string };

res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=86400');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar redirect

Comment on lines 7 to 12
export const getServerSideProps: GetServerSideProps = async (context) => {
const { res } = context;
const { slug } = context.params as { slug: string };

res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=86400');
return {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redirect

Comment on lines +8 to +11
const { res } = context;
const { slug } = context.params as { slug: string };

res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=86400');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redirect

Comment on lines +8 to +11
const { res } = context;
const { slug } = context.params as { slug: string };

res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=86400');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redirect

Comment on lines +8 to +10
const { res } = context;
const { subid } = context.query;
res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=86400');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redirect

a20hek
a20hek previously approved these changes Jun 1, 2026
@a20hek a20hek dismissed their stale review June 1, 2026 05:58

accidental

@devanshk404 devanshk404 marked this pull request as draft June 1, 2026 19:16
- Remove cache headers from 6 redirect-only pages (permanent redirects
  don't need s-maxage; browsers cache 301s natively)
- Remove cache header from earn/t/[slug]/edit.tsx (user-specific page,
  not public per PR intent)
- Gate s-maxage on successful data fetches in listing/[slug]/index.tsx,
  winner.tsx, and templates/listings/[slug]/index.tsx so null/error
  responses are never cached at the CDN
- Use no-store in catch/error paths for member-perks.tsx, projects.tsx,
  and listing/[slug]/submission/index.tsx to prevent caching transient
  failures as empty content
- Add @ts-ignore on browserToTerminal (valid Next.js option, missing
  from type definitions — matches fix already on main)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@devanshk404 devanshk404 marked this pull request as ready for review June 2, 2026 12:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/pages/earn/regions/[slug]/index.tsx (1)

130-131: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use GetServerSidePropsContext instead of NextPageContext.

The function signature uses NextPageContext, which is the type for getInitialProps and legacy patterns. For getServerSideProps, the correct type is GetServerSidePropsContext from next. This ensures proper type safety for the res object and other context properties.

🔧 Proposed fix
-import type { NextPageContext } from 'next';
+import type { GetServerSidePropsContext } from 'next';

-export async function getServerSideProps(context: NextPageContext) {
+export async function getServerSideProps(context: GetServerSidePropsContext) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/earn/regions/`[slug]/index.tsx around lines 130 - 131, The
getServerSideProps function currently types its context as NextPageContext;
update its signature to use GetServerSidePropsContext from 'next' so the context
(including res) has the correct types. Replace NextPageContext with
GetServerSidePropsContext in the getServerSideProps declaration and import
GetServerSidePropsContext at the top of the file to ensure proper typing for res
and other server-side properties.
src/pages/earn/listing/[slug]/submission/index.tsx (1)

57-65: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid caching the null bounty response.

When bounty is null, the code sets s-maxage=60, stale-while-revalidate=600, which caches the "not found" response at the CDN. If the bounty is temporarily unavailable due to a transient database issue or race condition, this will cache the empty state for 60 seconds, degrading user experience. The error path at line 78 correctly uses no-store for this reason.

Consider either:

  1. Removing the cache header for the null bounty path (no header = default private, no-store behavior)
  2. Using no-store to be explicit
🛡️ Proposed fix to prevent caching null bounty
     if (!bounty) {
-      res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
+      res.setHeader('Cache-Control', 'no-store');
       return {
         props: {
           slug,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/earn/listing/`[slug]/submission/index.tsx around lines 57 - 65, The
null-bounty branch currently sets Cache-Control to 's-maxage=60,
stale-while-revalidate=600' which caches the "not found" response; update the
branch handling when bounty is falsy (the block that checks "if (!bounty)") to
avoid CDN caching by removing the res.setHeader call or replacing it with
'Cache-Control: no-store' so the null/temporary-unavailable response is not
cached; adjust only the header behavior in that if (!bounty) branch (leave the
error path using no-store unchanged).
🧹 Nitpick comments (1)
src/pages/earn/regions/[slug]/index.tsx (1)

169-169: 💤 Low value

Remove unnecessary optional chaining on res.setHeader.

The optional chaining (res?.setHeader) is unnecessary because in getServerSideProps, the res object is always present in the context. Once the type is corrected to GetServerSidePropsContext, TypeScript will properly reflect that res is non-nullable.

♻️ Simplify by removing optional chaining
-    res?.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
+    res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
-    res?.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');
+    res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600');

Also applies to: 178-178

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/earn/regions/`[slug]/index.tsx at line 169, Remove the unnecessary
optional chaining on the response object in the server-side props handler:
locate the getServerSideProps function (exported as getServerSideProps) and
replace any occurrences of res?.setHeader(...) with res.setHeader(...); do this
for both occurrences (the one that sets 'Cache-Control' and the other at the
later call) and ensure the page's context type is GetServerSidePropsContext so
TypeScript treats res as non-nullable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/pages/earn/listing/`[slug]/submission/index.tsx:
- Around line 57-65: The null-bounty branch currently sets Cache-Control to
's-maxage=60, stale-while-revalidate=600' which caches the "not found" response;
update the branch handling when bounty is falsy (the block that checks "if
(!bounty)") to avoid CDN caching by removing the res.setHeader call or replacing
it with 'Cache-Control: no-store' so the null/temporary-unavailable response is
not cached; adjust only the header behavior in that if (!bounty) branch (leave
the error path using no-store unchanged).

In `@src/pages/earn/regions/`[slug]/index.tsx:
- Around line 130-131: The getServerSideProps function currently types its
context as NextPageContext; update its signature to use
GetServerSidePropsContext from 'next' so the context (including res) has the
correct types. Replace NextPageContext with GetServerSidePropsContext in the
getServerSideProps declaration and import GetServerSidePropsContext at the top
of the file to ensure proper typing for res and other server-side properties.

---

Nitpick comments:
In `@src/pages/earn/regions/`[slug]/index.tsx:
- Line 169: Remove the unnecessary optional chaining on the response object in
the server-side props handler: locate the getServerSideProps function (exported
as getServerSideProps) and replace any occurrences of res?.setHeader(...) with
res.setHeader(...); do this for both occurrences (the one that sets
'Cache-Control' and the other at the later call) and ensure the page's context
type is GetServerSidePropsContext so TypeScript treats res as non-nullable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 08301b6d-fc2d-4e85-b4aa-6721f31809e4

📥 Commits

Reviewing files that changed from the base of the PR and between c4d3053 and 6c58f5a.

📒 Files selected for processing (11)
  • next.config.ts
  • src/pages/earn/hackathon/talent-olympics.tsx
  • src/pages/earn/listing/[slug]/index.tsx
  • src/pages/earn/listing/[slug]/submission/index.tsx
  • src/pages/earn/listing/[slug]/winner.tsx
  • src/pages/earn/regions/[slug]/all.tsx
  • src/pages/earn/regions/[slug]/index.tsx
  • src/pages/earn/templates/listings/[slug]/index.tsx
  • src/pages/index.tsx
  • src/pages/member-perks.tsx
  • src/pages/projects.tsx
✅ Files skipped from review due to trivial changes (1)
  • next.config.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/pages/projects.tsx
  • src/pages/earn/templates/listings/[slug]/index.tsx
  • src/pages/earn/listing/[slug]/winner.tsx
  • src/pages/earn/listing/[slug]/index.tsx
  • src/pages/index.tsx
  • src/pages/earn/hackathon/talent-olympics.tsx
  • src/pages/earn/regions/[slug]/all.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants