Skip to content

fix: improve SEO for listing and hackathon pages#1349

Open
devanshk404 wants to merge 2 commits intoSuperteamDAO:mainfrom
devanshk404:fix/seo-listing-pages-ssr
Open

fix: improve SEO for listing and hackathon pages#1349
devanshk404 wants to merge 2 commits intoSuperteamDAO:mainfrom
devanshk404:fix/seo-listing-pages-ssr

Conversation

@devanshk404
Copy link
Copy Markdown
Contributor

@devanshk404 devanshk404 commented Mar 19, 2026

Summary

  • Layer 1 — Hackathon meta tags: Added missing <Meta> to /hackathon/all (had none); updated all 8 individual hackathon page titles to [Name] — Submission Tracks | Superteam Earn with specific descriptions replacing the old generic copy
  • Layer 2 — SSR content prefetch: Listing and grant content now ships in the initial HTML so Google sees real data without waiting for JS; implemented via React Query dehydration pattern — getServerSideProps prefetches the default view directly via Prisma, dehydrates into page props, HydrationBoundary in _app.tsx rehydrates on client, staleTime: 60s prevents immediate re-fetch

Pages changed

Page Change
/hackathon/all Added Meta (was missing entirely)
/hackathon/radar, /breakout, /cypherpunk, /mobius, /redacted, /renaissance, /scribes, /talent-olympics Title + description updated
/earn/grants Added getServerSideProps with Prisma prefetch
/earn/all, /earn/bounties, /earn/projects Extended getServerSideProps with Prisma prefetch
useListings, useGrants Added staleTime: 60 * 1000
_app.tsx Added HydrationBoundary

Technical notes

  • Query keys normalize undefined → null to survive JSON.parse/JSON.stringify serialization through Next.js props
  • Prisma Date objects serialized with JSON.parse(JSON.stringify(...)) before passing through getServerSideProps
  • Only the default view (first tab, category: All, status: open) is SSR'd; tab switches and pagination remain client-side

Test plan

  • View page source (not DevTools) on /earn/grants, /earn/all, /earn/bounties — listing/grant cards should be visible in raw HTML
  • Open Network tab, hard-refresh /earn/grants — no /api/grants request should fire on initial load (data comes from SSR)
  • Check <title> tag on all /earn/hackathon/* pages matches [Name] — Submission Tracks | Superteam Earn
  • Confirm no double-fetch within 60s of page load

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Performance

    • Optimized data caching to reduce redundant API calls and improve app responsiveness.
    • Implemented server-side data prefetching for faster initial page loads on listings, projects, and grants pages.
  • SEO

    • Enhanced hackathon page titles and descriptions for better search visibility and clearer information about submission tracks and earning opportunities.

Layer 1 — Hackathon meta tags:
- Add missing Meta to /hackathon/all (had none)
- Update all individual hackathon page titles to include hackathon
  name + "Submission Tracks" (Radar, Renaissance, Scribes, Breakout,
  Cypherpunk, Mobius, Redacted, Talent Olympics)
- Replace generic descriptions with hackathon-specific copy

Layer 2 — SSR content prefetch for listing pages:
- Add HydrationBoundary to _app.tsx to enable React Query dehydration
- Add staleTime (60s) to useGrants and useListings to prevent
  immediate client refetch of SSR-prefetched data
- Add getServerSideProps to /grants: prefetches all grants via Prisma
  directly, dehydrates into page props
- Extend getServerSideProps on /all, /bounties, /projects: prefetches
  default listing view (tab/category/status defaults) via Prisma,
  dehydrates into page props
- Normalize undefined → null in useListings query key to ensure
  dehydrated state keys survive JSON serialization
- Use JSON.parse/JSON.stringify on dehydrated state and Prisma results
  to handle Date objects in Next.js props serialization

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

vercel bot commented Mar 19, 2026

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

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 19, 2026

Warning

Rate limit exceeded

@devanshk404 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 3 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 11 minutes and 3 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 54f880c7-e5f4-4e74-8770-d91f6de34ece

📥 Commits

Reviewing files that changed from the base of the PR and between 31f43bd and 270d511.

📒 Files selected for processing (8)
  • src/pages/earn/hackathon/breakout.tsx
  • src/pages/earn/hackathon/cypherpunk.tsx
  • src/pages/earn/hackathon/mobius.tsx
  • src/pages/earn/hackathon/radar.tsx
  • src/pages/earn/hackathon/redacted.tsx
  • src/pages/earn/hackathon/renaissance.tsx
  • src/pages/earn/hackathon/scribes.tsx
  • src/pages/earn/hackathon/talent-olympics.tsx

Walkthrough

This PR introduces server-side React Query prefetching across multiple earn pages, adds staleTime configuration to query hooks for cache freshness control, wraps the app component in a HydrationBoundary for client hydration, and updates SEO metadata titles and descriptions across hackathon pages.

Changes

Cohort / File(s) Summary
Query Hook Configuration
src/features/grants/hooks/useGrants.ts, src/features/listings/hooks/useListings.ts
Added staleTime: 60 * 1000 to React Query options; useListings also normalizes optional parameters to null to standardize query cache keys.
App-Level Hydration
src/pages/_app.tsx
Wrapped page component in HydrationBoundary with dehydratedState from props to enable client-side query state rehydration.
Server-Side Query Prefetching
src/pages/earn/all/index.tsx, src/pages/earn/bounties/index.tsx, src/pages/earn/grants/index.tsx, src/pages/earn/projects/index.tsx
Added getServerSideProps implementations that create a QueryClient, prefetch queries from Prisma using buildListingQuery/buildGrantsQuery, apply post-processing via reorderFeaturedOngoing, and return dehydrated state alongside existing props.
Hackathon SEO Metadata
src/pages/earn/hackathon/all.tsx, src/pages/earn/hackathon/breakout.tsx, src/pages/earn/hackathon/cypherpunk.tsx, src/pages/earn/hackathon/mobius.tsx, src/pages/earn/hackathon/radar.tsx, src/pages/earn/hackathon/redacted.tsx, src/pages/earn/hackathon/renaissance.tsx, src/pages/earn/hackathon/scribes.tsx, src/pages/earn/hackathon/talent-olympics.tsx
Updated page-level Meta titles to include "— Submission Tracks" and refreshed descriptions to emphasize bounty track submission and earning mechanics.

Sequence Diagram

sequenceDiagram
    participant Browser as Browser (Client)
    participant Server as Next.js Server
    participant QueryClient as QueryClient
    participant Database as Prisma / Database
    participant HB as HydrationBoundary

    Browser->>Server: Request page (e.g., /earn/all)
    activate Server
    Server->>QueryClient: new QueryClient()
    activate QueryClient
    
    Server->>Database: buildListingQuery() + prisma.bounties.findMany()
    activate Database
    Database-->>Server: Bounty records
    deactivate Database
    
    Server->>Server: reorderFeaturedOngoing(results)
    Server->>QueryClient: prefetchQuery(queryKey, transformedData)
    QueryClient-->>Server: Query cached
    deactivate QueryClient
    
    Server->>Server: dehydrateQueryClient()
    Server-->>Browser: HTML + dehydratedState prop
    deactivate Server
    
    activate Browser
    Browser->>HB: Render with dehydratedState
    activate HB
    HB->>HB: Hydrate queries from dehydratedState
    HB-->>Browser: Interactive page with cached data
    deactivate HB
    deactivate Browser
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • chore: redacted updates #983: Modifies src/pages/earn/hackathon/redacted.tsx Meta fields, directly related to the redacted.tsx Meta title update in this PR.

Poem

🐰 Hop, hop! The queries are prefetched before the hop,
Server whispers to client, "Here's your state, don't drop!"
Hydration flows smoothly from database to sight,
Tracks align and titles gleam—hackathons shine bright!

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: improving SEO across listing and hackathon pages through server-side rendering, metadata updates, and query optimization.

✏️ 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/features/grants/hooks/useGrants.ts (1)

50-59: ⚠️ Potential issue | 🟠 Major

Query key mismatch will prevent SSR hydration from working.

The queryKey uses raw undefined values for optional parameters (region, sponsor, skill), but the server-side prefetch in grants/index.tsx passes through JSON.parse(JSON.stringify(...)) which converts undefined to null in arrays. This creates a cache key mismatch:

  • Server key after serialization: ['grants', 'all', 'All', null, null, null]
  • Client key: ['grants', 'all', 'All', undefined, undefined, undefined]

React Query won't find the prefetched data, causing an unnecessary client-side refetch.

Proposed fix: Normalize optional params to null
 export function useGrants({
   context,
   category,
   region,
   sponsor,
   skill,
 }: GrantsParams) {
   return useQuery({
-    queryKey: ['grants', context, category, region, sponsor, skill],
+    queryKey: [
+      'grants',
+      context,
+      category,
+      region ?? null,
+      sponsor ?? null,
+      skill ?? null,
+    ],
     queryFn: () =>
       fetchGrants({
         context,
         category,
         region,
         sponsor,
         skill,
       }),
     staleTime: 60 * 1000,
   });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/grants/hooks/useGrants.ts` around lines 50 - 59, The query key
in useGrants (the queryKey array built in the hook) must normalize optional
parameters to null to match server-side serialization; update the construction
of the queryKey (and any place that computes region, sponsor, skill for the key)
to replace undefined with null (e.g., use region ?? null, sponsor ?? null, skill
?? null) so the client key matches the server-prefetched key and SSR hydration
succeeds for the useGrants query.
🧹 Nitpick comments (5)
src/pages/earn/hackathon/cypherpunk.tsx (1)

194-195: Remove debug console logs.

These console.log statements appear to be debugging artifacts and should be removed from production code to keep the browser console clean.

🧹 Proposed cleanup
   useEffect(() => {
-    console.log('start date', START_DATE);
-    console.log('close date', CLOSE_DATE);
     function updateStatus() {
       if (dayjs().isAfter(dayjs(CLOSE_DATE))) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/earn/hackathon/cypherpunk.tsx` around lines 194 - 195, Remove the
two debug console.log calls that print START_DATE and CLOSE_DATE; locate the
logs in the component file where START_DATE and CLOSE_DATE are referenced (the
lines containing console.log('start date', START_DATE) and console.log('close
date', CLOSE_DATE)) and delete them so no debug output is left in production
code.
src/pages/earn/grants/index.tsx (1)

103-126: Use explicit null values in queryKey for consistency.

The queryKey uses undefined values that will be converted to null during JSON serialization. For clarity and to match the client-side hook pattern (after the fix I suggested for useGrants.ts), use explicit null values.

Proposed fix
   await queryClient.prefetchQuery({
-    queryKey: ['grants', 'all', 'All', undefined, undefined, undefined],
+    queryKey: ['grants', 'all', 'All', null, null, null],
     queryFn: async () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/earn/grants/index.tsx` around lines 103 - 126, The queryKey passed
to queryClient.prefetchQuery uses undefined entries which will be serialized to
null; update the array in the prefetchQuery call (the queryKey inside the async
block that calls buildGrantsQuery and prisma.grants.findMany) to use explicit
null values instead of undefined (e.g., replace the last three undefineds with
null) so it matches the client-side useGrants.ts pattern and keeps keys
consistent with buildGrantsQuery/query hooks.
src/pages/earn/bounties/index.tsx (2)

59-95: Consider extracting shared SSR prefetch logic.

The prefetch logic is nearly identical to projects/index.tsx (and likely all/index.tsx), differing only in the tab parameter. Consider extracting a shared helper:

♻️ Example shared helper
// src/features/listings/utils/ssr-prefetch.ts
export async function prefetchListingsQuery(
  queryClient: QueryClient,
  tab: 'all' | 'bounties' | 'projects',
) {
  await queryClient.prefetchQuery({
    queryKey: ['listings', 'all', tab, 'All', 'open', 'Date', 'asc', null, null, null, false],
    queryFn: async () => {
      const { where, orderBy, take } = await buildListingQuery(
        { context: 'all', tab, category: 'All', status: 'open', sortBy: 'Date', order: 'asc' },
        null,
      );
      const listings = await prisma.bounties.findMany({
        where, orderBy, take, select: listingSelect,
      });
      return JSON.parse(JSON.stringify(reorderFeaturedOngoing(listings)));
    },
  });
}

This would reduce duplication and ensure consistency across pages.

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

In `@src/pages/earn/bounties/index.tsx` around lines 59 - 95, Extract the repeated
SSR prefetch logic into a shared helper (e.g., prefetchListingsQuery) that
accepts a QueryClient and a tab value ('all' | 'bounties' | 'projects'); move
the queryKey assembly and queryFn there and keep it calling buildListingQuery,
prisma.bounties.findMany (with listingSelect), and reorderFeaturedOngoing as-is,
then replace the inline queryClient.prefetchQuery call in this file with a call
to prefetchListingsQuery(queryClient, 'bounties') to remove duplication and
centralize behavior.

16-18: Same type inconsistency: dehydratedState returned but not in interface.

Same issue as in projects/index.tsx. Consider adding dehydratedState to the interface for type completeness.

🔧 Suggested fix
+import type { DehydratedState } from '@tanstack/react-query';

 interface BountiesPageProps {
   readonly potentialSession: boolean;
+  readonly dehydratedState: DehydratedState;
 }

Also applies to: 97-102

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

In `@src/pages/earn/bounties/index.tsx` around lines 16 - 18, The
BountiesPageProps interface is missing the dehydratedState property returned
from getServerSideProps/getStaticProps; update interface BountiesPageProps to
include dehydratedState (e.g., dehydratedState?: DehydratedState | unknown) and
import the correct DehydratedState type from react-query (or use appropriate
type/unknown) so the component signature and any props usage (BountiesPageProps,
dehydratedState) are type-consistent.
src/pages/earn/projects/index.tsx (1)

17-19: Props interface doesn't include dehydratedState but it's returned from getServerSideProps.

The GetServerSideProps<ProjectsPageProps> generic constrains the return type, but dehydratedState is returned without being declared in the interface. While this works at runtime (Next.js passes it to _app.tsx via pageProps), the typing is incomplete.

🔧 Suggested fix
+import type { DehydratedState } from '@tanstack/react-query';

 interface ProjectsPageProps {
   readonly potentialSession: boolean;
+  readonly dehydratedState: DehydratedState;
 }

Also applies to: 100-105

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

In `@src/pages/earn/projects/index.tsx` around lines 17 - 19, The
ProjectsPageProps interface is missing the dehydratedState property that
getServerSideProps returns; update ProjectsPageProps to include dehydratedState
(e.g. dehydratedState?: DehydratedState) and import the correct DehydratedState
type from your query library (e.g. '@tanstack/react-query' or 'react-query') or
use a safe fallback type (Record<string, any>) so the
GetServerSideProps<ProjectsPageProps> generic is correctly satisfied; key
symbols: ProjectsPageProps, getServerSideProps, dehydratedState.
🤖 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/all/index.tsx`:
- Around line 48-61: The prefetch uses a hardcoded authenticated:false in
queryClient.prefetchQuery which will mismatch the client-side useListings call
that uses the real auth state from usePrivy() (causing cache misses); fix by
deriving the authenticated flag on the server from the same request/session
context you use for hydration (e.g., check cookies/session/user id) and replace
the hardcoded false in the query key with that computed boolean so the query key
used in prefetchQuery matches the authenticated value used by
ListingsSection/useListings on the client.
- Around line 84-89: The SSR prefetch uses a hardcoded authenticated: false in
the queryKey while ListingsSection calls useListings with the runtime usePrivy
authenticated value, causing key mismatch; update the server-side prefetch to
use the same authenticated value that the client will use (pass potentialSession
into the prefetch/queryKey instead of hardcoding false), and adjust
ListingsSection/useListings to accept and use that potentialSession prop (or
alternatively have ListingsSection call useListings with potentialSession rather
than usePrivy) so the queryKey used during dehydrate/hydration matches the
client-side key.

---

Outside diff comments:
In `@src/features/grants/hooks/useGrants.ts`:
- Around line 50-59: The query key in useGrants (the queryKey array built in the
hook) must normalize optional parameters to null to match server-side
serialization; update the construction of the queryKey (and any place that
computes region, sponsor, skill for the key) to replace undefined with null
(e.g., use region ?? null, sponsor ?? null, skill ?? null) so the client key
matches the server-prefetched key and SSR hydration succeeds for the useGrants
query.

---

Nitpick comments:
In `@src/pages/earn/bounties/index.tsx`:
- Around line 59-95: Extract the repeated SSR prefetch logic into a shared
helper (e.g., prefetchListingsQuery) that accepts a QueryClient and a tab value
('all' | 'bounties' | 'projects'); move the queryKey assembly and queryFn there
and keep it calling buildListingQuery, prisma.bounties.findMany (with
listingSelect), and reorderFeaturedOngoing as-is, then replace the inline
queryClient.prefetchQuery call in this file with a call to
prefetchListingsQuery(queryClient, 'bounties') to remove duplication and
centralize behavior.
- Around line 16-18: The BountiesPageProps interface is missing the
dehydratedState property returned from getServerSideProps/getStaticProps; update
interface BountiesPageProps to include dehydratedState (e.g., dehydratedState?:
DehydratedState | unknown) and import the correct DehydratedState type from
react-query (or use appropriate type/unknown) so the component signature and any
props usage (BountiesPageProps, dehydratedState) are type-consistent.

In `@src/pages/earn/grants/index.tsx`:
- Around line 103-126: The queryKey passed to queryClient.prefetchQuery uses
undefined entries which will be serialized to null; update the array in the
prefetchQuery call (the queryKey inside the async block that calls
buildGrantsQuery and prisma.grants.findMany) to use explicit null values instead
of undefined (e.g., replace the last three undefineds with null) so it matches
the client-side useGrants.ts pattern and keeps keys consistent with
buildGrantsQuery/query hooks.

In `@src/pages/earn/hackathon/cypherpunk.tsx`:
- Around line 194-195: Remove the two debug console.log calls that print
START_DATE and CLOSE_DATE; locate the logs in the component file where
START_DATE and CLOSE_DATE are referenced (the lines containing
console.log('start date', START_DATE) and console.log('close date', CLOSE_DATE))
and delete them so no debug output is left in production code.

In `@src/pages/earn/projects/index.tsx`:
- Around line 17-19: The ProjectsPageProps interface is missing the
dehydratedState property that getServerSideProps returns; update
ProjectsPageProps to include dehydratedState (e.g. dehydratedState?:
DehydratedState) and import the correct DehydratedState type from your query
library (e.g. '@tanstack/react-query' or 'react-query') or use a safe fallback
type (Record<string, any>) so the GetServerSideProps<ProjectsPageProps> generic
is correctly satisfied; key symbols: ProjectsPageProps, getServerSideProps,
dehydratedState.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 18e60055-7f83-4d01-b97e-111dfbd0cdb5

📥 Commits

Reviewing files that changed from the base of the PR and between 490b36d and 31f43bd.

📒 Files selected for processing (16)
  • src/features/grants/hooks/useGrants.ts
  • src/features/listings/hooks/useListings.ts
  • src/pages/_app.tsx
  • src/pages/earn/all/index.tsx
  • src/pages/earn/bounties/index.tsx
  • src/pages/earn/grants/index.tsx
  • src/pages/earn/hackathon/all.tsx
  • src/pages/earn/hackathon/breakout.tsx
  • src/pages/earn/hackathon/cypherpunk.tsx
  • src/pages/earn/hackathon/mobius.tsx
  • src/pages/earn/hackathon/radar.tsx
  • src/pages/earn/hackathon/redacted.tsx
  • src/pages/earn/hackathon/renaissance.tsx
  • src/pages/earn/hackathon/scribes.tsx
  • src/pages/earn/hackathon/talent-olympics.tsx
  • src/pages/earn/projects/index.tsx

Comment on lines +48 to +61
await queryClient.prefetchQuery({
queryKey: [
'listings',
'all',
'all',
'All',
'open',
'Date',
'asc',
null,
null,
null,
false,
],
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for useListings usage in ListingsSection to check authenticated parameter
ast-grep --pattern $'useListings({
  $$$
})'

# Also check ListingsSection component for potentialSession/authenticated handling
rg -n -A 20 'function ListingsSection' --type ts --type tsx

Repository: SuperteamDAO/earn

Length of output: 1018


🏁 Script executed:

# Find ListingsSection component file and check authenticated initialization
rg -n "authenticated\s*=" src/features/listings/components/ListingsSection.tsx | head -20

# Also check imports to understand potentialSession usage
rg -n -B 5 -A 5 "useListings" src/features/listings/components/ListingsSection.tsx | head -40

Repository: SuperteamDAO/earn

Length of output: 1174


🏁 Script executed:

# Get the full ListingsSection component to find authenticated initialization
sed -n '1,200p' src/features/listings/components/ListingsSection.tsx | cat -n

# Also search specifically for where authenticated is assigned
rg -n "authenticated\s*[:=]" src/features/listings/components/ListingsSection.tsx

Repository: SuperteamDAO/earn

Length of output: 7266


🏁 Script executed:

# Check the all/index.tsx file for the SSR context
sed -n '40,85p' src/pages/earn/all/index.tsx | cat -n

Repository: SuperteamDAO/earn

Length of output: 1372


Query key mismatch: authenticated parameter must align between SSR and client.

The prefetch query key hardcodes authenticated: false (line 60), but the client-side ListingsSection component passes the actual authenticated state from usePrivy() to useListings (line 183 of ListingsSection.tsx). For any authenticated user, the query keys will not match, causing a cache miss and defeating the SSR prefetch optimization.

The server should derive the authenticated value from the same source used for client hydration. Consider checking the authentication state from the request context (e.g., session or user ID hint from cookies) and passing that value to the prefetch query key so it aligns with what the client will request during hydration.

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

In `@src/pages/earn/all/index.tsx` around lines 48 - 61, The prefetch uses a
hardcoded authenticated:false in queryClient.prefetchQuery which will mismatch
the client-side useListings call that uses the real auth state from usePrivy()
(causing cache misses); fix by deriving the authenticated flag on the server
from the same request/session context you use for hydration (e.g., check
cookies/session/user id) and replace the hardcoded false in the query key with
that computed boolean so the query key used in prefetchQuery matches the
authenticated value used by ListingsSection/useListings on the client.

Comment on lines +84 to +89
return {
props: {
potentialSession: cookieExists,
dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))),
},
};
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how ListingsSection uses potentialSession prop
rg -n -B 5 -A 30 'export.*function ListingsSection|export const ListingsSection' --type ts

# Check if potentialSession is passed to useListings as authenticated
rg -n 'potentialSession|authenticated' src/features/listings/components/ListingsSection.tsx

Repository: SuperteamDAO/earn

Length of output: 3506


🏁 Script executed:

# Check the page file and how ListingsSection is used
rg -n -B 10 -A 10 'ListingsSection' src/pages/earn/all/index.tsx | head -80

# Also check what useListings does with authenticated parameter
rg -n -A 20 'export.*function useListings|export const useListings' src/features/listings

Repository: SuperteamDAO/earn

Length of output: 4584


🏁 Script executed:

# Get the complete getServerSideProps function
sed -n '40,100p' src/pages/earn/all/index.tsx

# Check if there's prefetching of useListings with authenticated parameter
rg -n 'prefetchQuery.*useListings\|setQueryData.*listings' src/pages/earn/all/index.tsx

Repository: SuperteamDAO/earn

Length of output: 1178


🏁 Script executed:

# Get the full ListingsSection component to see how useListings is called
cat -n src/features/listings/components/ListingsSection.tsx | head -200

Repository: SuperteamDAO/earn

Length of output: 7266


Fix the queryKey mismatch between SSR prefetch and client-side useListings call.

The SSR prefetch at line 48 hardcodes authenticated: false in the queryKey, but ListingsSection calls useListings with the authenticated value from the usePrivy hook (line 183). If Privy's authenticated state differs from the hardcoded false value during hydration, the queryKey won't match and the prefetched data won't be used. Either ensure the prefetch uses the same authenticated value that will be used on the client, or have ListingsSection pass a consistent value (such as from potentialSession) to useListings.

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

In `@src/pages/earn/all/index.tsx` around lines 84 - 89, The SSR prefetch uses a
hardcoded authenticated: false in the queryKey while ListingsSection calls
useListings with the runtime usePrivy authenticated value, causing key mismatch;
update the server-side prefetch to use the same authenticated value that the
client will use (pass potentialSession into the prefetch/queryKey instead of
hardcoding false), and adjust ListingsSection/useListings to accept and use that
potentialSession prop (or alternatively have ListingsSection call useListings
with potentialSession rather than usePrivy) so the queryKey used during
dehydrate/hydration matches the client-side key.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 23, 2026

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

Project Deployment Actions Updated (UTC)
earn Ready Ready Preview Apr 15, 2026 4:22am

Request Review

- Add getServerSideProps to radar, renaissance, scribes (new)
- Update getServerSideProps in breakout, cypherpunk, redacted to also
  prefetch tracks + stats via React Query dehydration
- Add BreadcrumbList + Event JSON-LD schema to all 8 hackathon pages
- mobius and talent-olympics get JSON-LD only (static data / custom gssp)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

2 participants