perf: add CDN cache headers to public getServerSideProps pages#1346
perf: add CDN cache headers to public getServerSideProps pages#1346devanshk404 wants to merge 3 commits into
Conversation
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>
|
@devanshk404 is attempting to deploy a commit to the Superteam Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughPR adds server-side Cache-Control headers to 20 Next.js page files' ChangesServer-side caching policy rollout
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 | 🟠 MajorUser-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 | 🟠 MajorGate 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 | 🟠 MajorAvoid caching error fallback responses.
Line 137 sets shared caching even when Line 132-135 has already fallen back to
bountyData = nullafter 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 | 🟠 MajorAvoid 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 | 🟠 MajorDo 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. Useno-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
getSubmissionsDatathrows 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-maxagefor 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
📒 Files selected for processing (28)
src/pages/earn/grants/[slug]/index.tsxsrc/pages/earn/grants/[slug]/references.tsxsrc/pages/earn/hackathon/breakout.tsxsrc/pages/earn/hackathon/mobius.tsxsrc/pages/earn/hackathon/redacted.tsxsrc/pages/earn/hackathon/talent-olympics.tsxsrc/pages/earn/listing/[slug]/index.tsxsrc/pages/earn/listing/[slug]/submission/index.tsxsrc/pages/earn/listing/[slug]/winner.tsxsrc/pages/earn/listings/[type]/[slug]/index.tsxsrc/pages/earn/listings/[type]/[slug]/submission/[subid].tsxsrc/pages/earn/listings/[type]/[slug]/submission/index.tsxsrc/pages/earn/listings/[type]/[slug]/winner.tsxsrc/pages/earn/listings/bounties/[slug]/index.tsxsrc/pages/earn/listings/bounties/[slug]/submission.tsxsrc/pages/earn/listings/grants/[slug].tsxsrc/pages/earn/opportunities/[[...tags]].tsxsrc/pages/earn/projects/all.tsxsrc/pages/earn/regions/[slug]/all.tsxsrc/pages/earn/regions/[slug]/index.tsxsrc/pages/earn/search/index.tsxsrc/pages/earn/skill/[slug].tsxsrc/pages/earn/t/[slug]/edit.tsxsrc/pages/earn/talent-olympics.tsxsrc/pages/earn/templates/listings/[slug]/index.tsxsrc/pages/index.tsxsrc/pages/member-perks.tsxsrc/pages/projects.tsx
| bountyData = null; | ||
| } | ||
|
|
||
| res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600'); |
There was a problem hiding this comment.
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.
| 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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| @@ -768,6 +769,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => { | |||
| }, | |||
| }); | |||
|
|
|||
| res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600'); | |||
There was a problem hiding this comment.
Remove from here, not a public facing page
| 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 { |
There was a problem hiding this comment.
It is a redirect no need for this
| const { res } = context; | ||
| const { slug } = context.params as { slug: string }; | ||
|
|
||
| res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=86400'); |
| 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 { |
| const { res } = context; | ||
| const { slug } = context.params as { slug: string }; | ||
|
|
||
| res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=86400'); |
| const { res } = context; | ||
| const { slug } = context.params as { slug: string }; | ||
|
|
||
| res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=86400'); |
| const { res } = context; | ||
| const { subid } = context.query; | ||
| res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=86400'); |
- 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>
There was a problem hiding this comment.
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 winUse
GetServerSidePropsContextinstead ofNextPageContext.The function signature uses
NextPageContext, which is the type forgetInitialPropsand legacy patterns. ForgetServerSideProps, the correct type isGetServerSidePropsContextfromnext. This ensures proper type safety for theresobject 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 winAvoid caching the null bounty response.
When
bountyis null, the code setss-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 usesno-storefor this reason.Consider either:
- Removing the cache header for the null bounty path (no header = default private, no-store behavior)
- Using
no-storeto 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 valueRemove unnecessary optional chaining on
res.setHeader.The optional chaining (
res?.setHeader) is unnecessary because ingetServerSideProps, theresobject is always present in the context. Once the type is corrected toGetServerSidePropsContext, TypeScript will properly reflect thatresis 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
📒 Files selected for processing (11)
next.config.tssrc/pages/earn/hackathon/talent-olympics.tsxsrc/pages/earn/listing/[slug]/index.tsxsrc/pages/earn/listing/[slug]/submission/index.tsxsrc/pages/earn/listing/[slug]/winner.tsxsrc/pages/earn/regions/[slug]/all.tsxsrc/pages/earn/regions/[slug]/index.tsxsrc/pages/earn/templates/listings/[slug]/index.tsxsrc/pages/index.tsxsrc/pages/member-perks.tsxsrc/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
Summary
s-maxage=60, stale-while-revalidate=600to 19 public data pages (listings, grants, hackathons, regions, skills, search, etc.)s-maxage=3600, stale-while-revalidate=86400to 9 redirect-only pagesProblem
All
getServerSidePropspages defaulted toCache-Control: private, no-store, causing every request — including Googlebot — to hit the origin server. This results in constantX-Vercel-Cache: MISSand unnecessary DB queries for pages that serve identical public data to all users.Test plan
X-Vercel-Cache: HITon second request for affected pages (e.g./earn/listing/[slug],/earn/grants/[slug])X-Vercel-Cache: MISS🤖 Generated with Claude Code
Summary by CodeRabbit