Skip to content

feat(ssr): server-render landing rows and first page of builders (fixes #410) - #429

Merged
0xdevcollins merged 3 commits into
boundlessfi:mainfrom
lmduy2612:feat/issue-410-ssr-landing-builders
Oct 22, 2026
Merged

0xdevcollins merged 3 commits into
boundlessfi:mainfrom
lmduy2612:feat/issue-410-ssr-landing-builders

Conversation

@lmduy2612

Copy link
Copy Markdown
Contributor

Summary

Server-renders the first view of the landing rows (/) and the directory (/builders) using TanStack Query v5 HydrationBoundary and dehydrate, prefetching data on the server with appropriate cache revalidation windows before handing off to the client query hooks.

Fixes #410

Changes Made

  1. Landing Page (/):

    • Prefetches the ecosystem stats strip (/users/directory?limit=1, /projects?limit=1, /organizations?limit=1), Top builders (/users/top-builders?limit=8), and Featured projects (/projects/featured).
    • Uses next: { revalidate: 180 } (3 minutes) cache window on server fetches to protect the API from repeated visits.
    • Wraps <StatsStrip />, <TopBuilders />, and <FeaturedProjects /> in <HydrationBoundary state={dehydrate(queryClient)}>.
    • Client components hydrate directly from the preloaded cache without duplicate network fetches on load.
  2. Builders Directory (/builders):

    • Parses search parameters on the server using parseBuildersQueryParams — sharing the exact same URL-parsing and defaulting logic as the client directory (readBuilderFilters, readBuilderPage, readBuilderSort).
    • Prefetches the first page of builders (buildersKeys.list(queryParams)) and filter facets (buildersKeys.filters) using next: { revalidate: 60 } (1 minute cache window).
    • Wraps the directory in <HydrationBoundary> so the first page renders directly in the server HTML while preserving client-side filtering, sorting, pagination, and debounce without full reloads.
  3. Resilience:

    • Server-side prefetch promises are handled with Promise.allSettled and dehydrate errors safely, ensuring that if the API is down or unreachable, the pages still render and display existing client error/fallback states rather than failing with HTTP 500.
    • All card components and interactive behaviors remain unchanged.

View-source Verification Snippets

1. Landing Page (/): Top builders rendered directly in server HTML

<h3 class="truncate text-base font-semibold text-foreground">Ofoekii Frances Onyinyechukwu</h3>
<p class="truncate text-sm text-muted-foreground">@bounder_tildapetal02</p>
<h3 class="truncate text-base font-semibold text-foreground">onovo victor</h3>
<p class="truncate text-sm text-muted-foreground">@bounder_onovovictor1</p>
<h3 class="truncate text-base font-semibold text-foreground">Collins Chikangwu</h3>
<p class="truncate text-sm text-muted-foreground">@0xdevcollins</p>

2. Directory Page (/builders): First page of builders rendered in server HTML

<h3 class="truncate text-base font-semibold text-foreground">onovo victor</h3>
<p class="truncate text-sm text-muted-foreground">@bounder_onovovictor1</p>
<h3 class="truncate text-base font-semibold text-foreground">Ofoekii Frances Onyinyechukwu</h3>
<p class="truncate text-sm text-muted-foreground">@bounder_tildapetal02</p>
<h3 class="truncate text-base font-semibold text-foreground">Collins Chikangwu</h3>
<p class="truncate text-sm text-muted-foreground">@0xdevcollins</p>

3. Filtered Directory Page (/builders?skills=UX%20Design): Filtered builders in server HTML

<h3 class="truncate text-base font-semibold text-foreground">onovo victor</h3>
<p class="truncate text-sm text-muted-foreground">@bounder_onovovictor1</p>

Verification Checklist

  • npm run lint passes (0 errors, 0 warnings)
  • npx tsc --noEmit passes (0 errors)
  • npm run build passes (Next.js 16 Turbopack production build)
  • npm run seo:check passes (all HTML & XML routes 200 and SEO tags present)
  • SSR HTML contains Top builders & Featured projects on /
  • SSR HTML contains filtered builders on /builders?skills=...
  • No duplicate client requests on first load
  • Changing filters/sort/page works seamlessly in the client without page reload
  • Unreachable API renders gracefully with error/fallback states

@vercel

vercel Bot commented Oct 18, 2026

Copy link
Copy Markdown

@lmduy2612 is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Oct 18, 2026

Copy link
Copy Markdown

@lmduy2612 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@0xdevcollins

Copy link
Copy Markdown
Contributor

Thanks @lmduy2612 , this works well. Two changes before merge:

Add a timeout to the server-side fetches (for example signal: AbortSignal.timeout(5000) in the init you pass). Without it, a hanging API hangs the whole page instead of falling back to the client states.
Drop the custom shouldDehydrateQuery and only dehydrate successful queries (the default), so a failed prefetch doesn’t put the error in the page.

Small cleanups: use the imported helpers directly instead of the const readFilters = ... aliases.

- Add AbortSignal.timeout(5000) to server-side prefetches in page.tsx and builders/page.tsx
- Remove custom shouldDehydrateQuery to only dehydrate successful queries by default
- Replace helper aliases with direct imports in builders-view.tsx
@lmduy2612

Copy link
Copy Markdown
Contributor Author

Thanks for the review @0xdevcollins! Updated in bdd01c0:

  • Added signal: AbortSignal.timeout(5000) to all server-side prefetches in app/page.tsx and app/builders/page.tsx.
  • Dropped the custom shouldDehydrateQuery so only successful queries are dehydrated into page state (default behavior).
  • Cleaned up aliases in components/discover/builders-view.tsx to use imported helpers and constants directly.

Verified locally with tsc --noEmit, npm run lint, and a clean next build.

@lmduy2612

Copy link
Copy Markdown
Contributor Author

Hi @0xdevcollins, gentle reminder as Wave 10 is ending tomorrow (Oct 22). All requested updates (AbortSignal timeout, default query hydration, and alias cleanup) have been completed and verified. Whenever you have a moment, could you please take a look and merge? Thank you!

Conflicts resolved:
- stats-strip.tsx: keep the shared STATS/fetchTotal from landing-queries.ts.
  Main (boundlessfi#433) dropped the Teams stat, so drop it from landing-queries.ts too
  (and PublicOrganizationListDto from CountedList); otherwise the move would
  have brought Teams back and prefetched /organizations on the server.
- featured-projects.tsx: keep the shared featured query, import
  toProjectCard from its new home next to it (boundlessfi#432) and keep mainAppUrl for
  "Browse all projects".
- builders-view.tsx: keep the shared URL parsing from use-builders.ts and the
  "How to get listed" hook-up from boundlessfi#428, in the file's current quote style.
@0xdevcollins
0xdevcollins merged commit f780bf8 into boundlessfi:main Oct 22, 2026
1 check failed
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.

Server-render the landing rows and the first page of builders

2 participants