fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender - #7

Merged
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation
May 12, 2026
Merged

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender#7
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation

Conversation

@agustinkassis

@agustinkassisagustinkassis commented May 12, 2026

Copy link
Copy Markdown
Member

Summary

On prod (https://lacrypta.dev/hackathons/commerce and every other hackathon page), the entire <HackathonProjectsList> section is missing from the rendered HTML. Users see the hero, calendar, sponsors, rules — and then nothing where the project list and links should be. On /hackathons/commerce this is especially visible: the page is Nostr-only (no curated projects-commerce.json), so the missing list means zero project links to click.

Diagnosis

Confirmed by comparing local SSR vs prod:

size"PROYECTOS INSCRIPTOS" in HTML"Rescanear Nostr" in HTML
Local dev102KB
Prod (Vercel)55KB

In the prod RSC stream, the page's main row references $L32 where the projects-list output should be, but row 32's data is never sent. The stream truncates right after the Participación rules card and jumps straight to metadata. Same shape on /hackathons/foundations.

Why specifically this slot:

  • app/hackathons/[id]/page.tsx is wrapped in "use cache" (line 284).
  • Inside, three client components are rendered: HackathonInscripcionButton (in hero), HackathonResultsClient (in awards card), and HackathonProjectsList (last child).
  • The first two render fine. Only the trailing one — which has the most hooks (useState, useRef, useMemo×3, useHackathonResults, useEffect) and the most transitive imports — gets dropped.

With Next 16 cacheComponents: true, prerendering a complex client component as a direct child of a "use cache" server function hits a streaming truncation: the prerender abandons the slot mid-stream and the rest of the body never arrives.

Fix

Wrap <HackathonProjectsList> in <Suspense fallback={null}>. This marks the slot as an explicit deferred streaming boundary, so the cached prerender stops trying to resolve it inline. Fallback is null (the component renders its own skeleton when nostrSubmissions === []). Client hydration takes over normally.

+ <Suspense fallback={null}>
<HackathonProjectsList hackathon={hackathon} />
+ </Suspense>

12-line diff to a single file, no behavioral change elsewhere.

Test plan

  • Vercel preview at <preview>/hackathons/commerce shows the project list with 6 Nostr submissions and clickable links.
  • curl -s <preview>/hackathons/commerce | wc -c ≈ 100KB+, not ~55KB.
  • curl -s <preview>/hackathons/commerce | grep -c 'Rescanear Nostr' ≥ 1.
  • Same checks on /hackathons/foundations (had the same truncation, just less visible because curated projects masked it).
  • Clicking any project link from /hackathons/commerce lands on /hackathons/commerce/<id> with the project page content.
  • Hard-reload the project page itself (existing path that did work) — still works, no regression.

Fallback if preview still truncates

If for some reason the Suspense wrap alone isn't sufficient on Vercel, the surgical follow-up is to move <HackathonProjectsList> out of the "use cache" boundary entirely — split the page into a cached sub-component (hero + cards) and a non-cached parent that renders both the cached part and the projects list. That keeps the cards prerendered statically while letting the dynamic list render uncached. Easy to apply as a second commit if needed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Optimized the hackathon projects section to load asynchronously, improving overall page loading experience.

Review Change Stack

…erender
On prod (Vercel), the `/hackathons/[id]` page is served with the entire
HackathonProjectsList section truncated from the RSC stream: the parent
row references $L32, but row 32's data is never sent. The page ends at
the "Participación" rules card and the projects list (with the project
links users want to click) simply doesn't render.
Local dev SSR includes the section fine (~102KB vs 55KB on prod).
The truncation is specific to the prerender path that combines a
`"use cache"` directive on the page with a complex client component
(useEffect + useMemo + useHackathonResults + lots of imports) as the
last child. Earlier client components on the page render fine
(HackathonInscripcionButton, HackathonResultsClient) — only the tail
gets dropped.
Wrapping `<HackathonProjectsList>` in `<Suspense fallback={null}>`
marks the slot as a deferred streaming boundary so the cached prerender
stops trying to resolve it inline. The fallback is null (the component
already renders its own loading skeleton when nostrSubmissions === []),
and client hydration takes over normally.
Verified locally: SSR HTML still contains "PROYECTOS INSCRIPTOS" /
"Rescanear Nostr" / "Inscribite gratis" after the change, and the
client component mounts correctly. The Vercel-side fix needs a preview
deploy to confirm row 32 actually arrives in the prod RSC stream now.
@vercel

vercelBot commented May 12, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreview, CommentMay 12, 2026 11:46pm

Request Review

@coderabbitai

coderabbitaiBot commented May 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds React Suspense support to the hackathon detail page by importing the Suspense component and wrapping the HackathonProjectsList section with a <Suspense fallback={null}> boundary. This defers rendering of the projects/leaderboard section, enabling streaming without blocking the parent page render.

Changes

Suspense boundary for projects streaming

Layer / File(s)Summary
Suspense boundary integration
app/hackathons/[id]/page.tsx
Add Suspense import from React and wrap HackathonProjectsList with <Suspense fallback={null}>, allowing the projects/leaderboard section to load asynchronously.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 A Suspense boundary, light and clean,
Lets projects load where they've never been,
No blocking streams, just async grace,
The hackathon finds its streaming pace! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and concisely identifies the main change: wrapping HackathonProjectsList in Suspense to fix a prerender issue. It accurately reflects the primary fix in the changeset.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hackathons-prerender-truncation

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/hackathons/[id]/page.tsx (1)

542-543: ⚡ Quick win

Clarify the comment about the fallback.

The comment states "The fallback renders the SSR skeleton" but fallback={null} renders nothing at the Suspense boundary. Based on the PR description, the component itself handles its own loading state. Consider revising for clarity:

- * comes back missing the entire projects section. The fallback renders- * the SSR skeleton; client takes over on hydration.+ * comes back missing the entire projects section. The null fallback+ * allows the component to render its own skeleton during client hydration.
🤖 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 `@app/hackathons/`[id]/page.tsx around lines 542 - 543, Update the inline
comment to accurately describe behavior at the Suspense boundary: state that
fallback={null} causes the Suspense to render nothing during fallback, and
clarify that the component (the child inside Suspense) is responsible for
rendering its own loading/SSR skeleton on hydration rather than the Suspense
boundary doing it; reference the Suspense usage and the fallback={null} prop so
reviewers can locate and revise the comment.
🤖 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.
Nitpick comments:
In `@app/hackathons/`[id]/page.tsx:
- Around line 542-543: Update the inline comment to accurately describe behavior
at the Suspense boundary: state that fallback={null} causes the Suspense to
render nothing during fallback, and clarify that the component (the child inside
Suspense) is responsible for rendering its own loading/SSR skeleton on hydration
rather than the Suspense boundary doing it; reference the Suspense usage and the
fallback={null} prop so reviewers can locate and revise the comment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 698aea5c-1f12-421a-8764-b1e4c689a7b7

📥 Commits

Reviewing files that changed from the base of the PR and between df9b0dc and edd6f8d.

📒 Files selected for processing (1)
  • app/hackathons/[id]/page.tsx

@agustinkassis
agustinkassis merged commit 07f827e into mainMay 12, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the fix/hackathons-prerender-truncation branch May 12, 2026 23:49
Sign up for freeto 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.

1 participant

@agustinkassis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender - #7

Merged
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation
May 12, 2026
Merged

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender#7
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation

Conversation

@agustinkassis

@agustinkassisagustinkassis commented May 12, 2026

Copy link
Copy Markdown
Member

Summary

On prod (https://lacrypta.dev/hackathons/commerce and every other hackathon page), the entire <HackathonProjectsList> section is missing from the rendered HTML. Users see the hero, calendar, sponsors, rules — and then nothing where the project list and links should be. On /hackathons/commerce this is especially visible: the page is Nostr-only (no curated projects-commerce.json), so the missing list means zero project links to click.

Diagnosis

Confirmed by comparing local SSR vs prod:

size"PROYECTOS INSCRIPTOS" in HTML"Rescanear Nostr" in HTML
Local dev102KB
Prod (Vercel)55KB

In the prod RSC stream, the page's main row references $L32 where the projects-list output should be, but row 32's data is never sent. The stream truncates right after the Participación rules card and jumps straight to metadata. Same shape on /hackathons/foundations.

Why specifically this slot:

  • app/hackathons/[id]/page.tsx is wrapped in "use cache" (line 284).
  • Inside, three client components are rendered: HackathonInscripcionButton (in hero), HackathonResultsClient (in awards card), and HackathonProjectsList (last child).
  • The first two render fine. Only the trailing one — which has the most hooks (useState, useRef, useMemo×3, useHackathonResults, useEffect) and the most transitive imports — gets dropped.

With Next 16 cacheComponents: true, prerendering a complex client component as a direct child of a "use cache" server function hits a streaming truncation: the prerender abandons the slot mid-stream and the rest of the body never arrives.

Fix

Wrap <HackathonProjectsList> in <Suspense fallback={null}>. This marks the slot as an explicit deferred streaming boundary, so the cached prerender stops trying to resolve it inline. Fallback is null (the component renders its own skeleton when nostrSubmissions === []). Client hydration takes over normally.

+ <Suspense fallback={null}>
<HackathonProjectsList hackathon={hackathon} />
+ </Suspense>

12-line diff to a single file, no behavioral change elsewhere.

Test plan

  • Vercel preview at <preview>/hackathons/commerce shows the project list with 6 Nostr submissions and clickable links.
  • curl -s <preview>/hackathons/commerce | wc -c ≈ 100KB+, not ~55KB.
  • curl -s <preview>/hackathons/commerce | grep -c 'Rescanear Nostr' ≥ 1.
  • Same checks on /hackathons/foundations (had the same truncation, just less visible because curated projects masked it).
  • Clicking any project link from /hackathons/commerce lands on /hackathons/commerce/<id> with the project page content.
  • Hard-reload the project page itself (existing path that did work) — still works, no regression.

Fallback if preview still truncates

If for some reason the Suspense wrap alone isn't sufficient on Vercel, the surgical follow-up is to move <HackathonProjectsList> out of the "use cache" boundary entirely — split the page into a cached sub-component (hero + cards) and a non-cached parent that renders both the cached part and the projects list. That keeps the cards prerendered statically while letting the dynamic list render uncached. Easy to apply as a second commit if needed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Optimized the hackathon projects section to load asynchronously, improving overall page loading experience.

Review Change Stack

…erender
On prod (Vercel), the `/hackathons/[id]` page is served with the entire
HackathonProjectsList section truncated from the RSC stream: the parent
row references $L32, but row 32's data is never sent. The page ends at
the "Participación" rules card and the projects list (with the project
links users want to click) simply doesn't render.
Local dev SSR includes the section fine (~102KB vs 55KB on prod).
The truncation is specific to the prerender path that combines a
`"use cache"` directive on the page with a complex client component
(useEffect + useMemo + useHackathonResults + lots of imports) as the
last child. Earlier client components on the page render fine
(HackathonInscripcionButton, HackathonResultsClient) — only the tail
gets dropped.
Wrapping `<HackathonProjectsList>` in `<Suspense fallback={null}>`
marks the slot as a deferred streaming boundary so the cached prerender
stops trying to resolve it inline. The fallback is null (the component
already renders its own loading skeleton when nostrSubmissions === []),
and client hydration takes over normally.
Verified locally: SSR HTML still contains "PROYECTOS INSCRIPTOS" /
"Rescanear Nostr" / "Inscribite gratis" after the change, and the
client component mounts correctly. The Vercel-side fix needs a preview
deploy to confirm row 32 actually arrives in the prod RSC stream now.
@vercel

vercelBot commented May 12, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreview, CommentMay 12, 2026 11:46pm

Request Review

@coderabbitai

coderabbitaiBot commented May 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds React Suspense support to the hackathon detail page by importing the Suspense component and wrapping the HackathonProjectsList section with a <Suspense fallback={null}> boundary. This defers rendering of the projects/leaderboard section, enabling streaming without blocking the parent page render.

Changes

Suspense boundary for projects streaming

Layer / File(s)Summary
Suspense boundary integration
app/hackathons/[id]/page.tsx
Add Suspense import from React and wrap HackathonProjectsList with <Suspense fallback={null}>, allowing the projects/leaderboard section to load asynchronously.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 A Suspense boundary, light and clean,
Lets projects load where they've never been,
No blocking streams, just async grace,
The hackathon finds its streaming pace! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and concisely identifies the main change: wrapping HackathonProjectsList in Suspense to fix a prerender issue. It accurately reflects the primary fix in the changeset.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hackathons-prerender-truncation

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/hackathons/[id]/page.tsx (1)

542-543: ⚡ Quick win

Clarify the comment about the fallback.

The comment states "The fallback renders the SSR skeleton" but fallback={null} renders nothing at the Suspense boundary. Based on the PR description, the component itself handles its own loading state. Consider revising for clarity:

- * comes back missing the entire projects section. The fallback renders- * the SSR skeleton; client takes over on hydration.+ * comes back missing the entire projects section. The null fallback+ * allows the component to render its own skeleton during client hydration.
🤖 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 `@app/hackathons/`[id]/page.tsx around lines 542 - 543, Update the inline
comment to accurately describe behavior at the Suspense boundary: state that
fallback={null} causes the Suspense to render nothing during fallback, and
clarify that the component (the child inside Suspense) is responsible for
rendering its own loading/SSR skeleton on hydration rather than the Suspense
boundary doing it; reference the Suspense usage and the fallback={null} prop so
reviewers can locate and revise the comment.
🤖 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.
Nitpick comments:
In `@app/hackathons/`[id]/page.tsx:
- Around line 542-543: Update the inline comment to accurately describe behavior
at the Suspense boundary: state that fallback={null} causes the Suspense to
render nothing during fallback, and clarify that the component (the child inside
Suspense) is responsible for rendering its own loading/SSR skeleton on hydration
rather than the Suspense boundary doing it; reference the Suspense usage and the
fallback={null} prop so reviewers can locate and revise the comment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 698aea5c-1f12-421a-8764-b1e4c689a7b7

📥 Commits

Reviewing files that changed from the base of the PR and between df9b0dc and edd6f8d.

📒 Files selected for processing (1)
  • app/hackathons/[id]/page.tsx

@agustinkassis
agustinkassis merged commit 07f827e into mainMay 12, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the fix/hackathons-prerender-truncation branch May 12, 2026 23:49
Sign up for freeto 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.

1 participant

@agustinkassis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender - #7

Merged
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation
May 12, 2026
Merged

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender#7
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation

Conversation

@agustinkassis

@agustinkassisagustinkassis commented May 12, 2026

Copy link
Copy Markdown
Member

Summary

On prod (https://lacrypta.dev/hackathons/commerce and every other hackathon page), the entire <HackathonProjectsList> section is missing from the rendered HTML. Users see the hero, calendar, sponsors, rules — and then nothing where the project list and links should be. On /hackathons/commerce this is especially visible: the page is Nostr-only (no curated projects-commerce.json), so the missing list means zero project links to click.

Diagnosis

Confirmed by comparing local SSR vs prod:

size"PROYECTOS INSCRIPTOS" in HTML"Rescanear Nostr" in HTML
Local dev102KB
Prod (Vercel)55KB

In the prod RSC stream, the page's main row references $L32 where the projects-list output should be, but row 32's data is never sent. The stream truncates right after the Participación rules card and jumps straight to metadata. Same shape on /hackathons/foundations.

Why specifically this slot:

  • app/hackathons/[id]/page.tsx is wrapped in "use cache" (line 284).
  • Inside, three client components are rendered: HackathonInscripcionButton (in hero), HackathonResultsClient (in awards card), and HackathonProjectsList (last child).
  • The first two render fine. Only the trailing one — which has the most hooks (useState, useRef, useMemo×3, useHackathonResults, useEffect) and the most transitive imports — gets dropped.

With Next 16 cacheComponents: true, prerendering a complex client component as a direct child of a "use cache" server function hits a streaming truncation: the prerender abandons the slot mid-stream and the rest of the body never arrives.

Fix

Wrap <HackathonProjectsList> in <Suspense fallback={null}>. This marks the slot as an explicit deferred streaming boundary, so the cached prerender stops trying to resolve it inline. Fallback is null (the component renders its own skeleton when nostrSubmissions === []). Client hydration takes over normally.

+ <Suspense fallback={null}>
<HackathonProjectsList hackathon={hackathon} />
+ </Suspense>

12-line diff to a single file, no behavioral change elsewhere.

Test plan

  • Vercel preview at <preview>/hackathons/commerce shows the project list with 6 Nostr submissions and clickable links.
  • curl -s <preview>/hackathons/commerce | wc -c ≈ 100KB+, not ~55KB.
  • curl -s <preview>/hackathons/commerce | grep -c 'Rescanear Nostr' ≥ 1.
  • Same checks on /hackathons/foundations (had the same truncation, just less visible because curated projects masked it).
  • Clicking any project link from /hackathons/commerce lands on /hackathons/commerce/<id> with the project page content.
  • Hard-reload the project page itself (existing path that did work) — still works, no regression.

Fallback if preview still truncates

If for some reason the Suspense wrap alone isn't sufficient on Vercel, the surgical follow-up is to move <HackathonProjectsList> out of the "use cache" boundary entirely — split the page into a cached sub-component (hero + cards) and a non-cached parent that renders both the cached part and the projects list. That keeps the cards prerendered statically while letting the dynamic list render uncached. Easy to apply as a second commit if needed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Optimized the hackathon projects section to load asynchronously, improving overall page loading experience.

Review Change Stack

…erender
On prod (Vercel), the `/hackathons/[id]` page is served with the entire
HackathonProjectsList section truncated from the RSC stream: the parent
row references $L32, but row 32's data is never sent. The page ends at
the "Participación" rules card and the projects list (with the project
links users want to click) simply doesn't render.
Local dev SSR includes the section fine (~102KB vs 55KB on prod).
The truncation is specific to the prerender path that combines a
`"use cache"` directive on the page with a complex client component
(useEffect + useMemo + useHackathonResults + lots of imports) as the
last child. Earlier client components on the page render fine
(HackathonInscripcionButton, HackathonResultsClient) — only the tail
gets dropped.
Wrapping `<HackathonProjectsList>` in `<Suspense fallback={null}>`
marks the slot as a deferred streaming boundary so the cached prerender
stops trying to resolve it inline. The fallback is null (the component
already renders its own loading skeleton when nostrSubmissions === []),
and client hydration takes over normally.
Verified locally: SSR HTML still contains "PROYECTOS INSCRIPTOS" /
"Rescanear Nostr" / "Inscribite gratis" after the change, and the
client component mounts correctly. The Vercel-side fix needs a preview
deploy to confirm row 32 actually arrives in the prod RSC stream now.
@vercel

vercelBot commented May 12, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreview, CommentMay 12, 2026 11:46pm

Request Review

@coderabbitai

coderabbitaiBot commented May 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds React Suspense support to the hackathon detail page by importing the Suspense component and wrapping the HackathonProjectsList section with a <Suspense fallback={null}> boundary. This defers rendering of the projects/leaderboard section, enabling streaming without blocking the parent page render.

Changes

Suspense boundary for projects streaming

Layer / File(s)Summary
Suspense boundary integration
app/hackathons/[id]/page.tsx
Add Suspense import from React and wrap HackathonProjectsList with <Suspense fallback={null}>, allowing the projects/leaderboard section to load asynchronously.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 A Suspense boundary, light and clean,
Lets projects load where they've never been,
No blocking streams, just async grace,
The hackathon finds its streaming pace! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and concisely identifies the main change: wrapping HackathonProjectsList in Suspense to fix a prerender issue. It accurately reflects the primary fix in the changeset.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hackathons-prerender-truncation

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/hackathons/[id]/page.tsx (1)

542-543: ⚡ Quick win

Clarify the comment about the fallback.

The comment states "The fallback renders the SSR skeleton" but fallback={null} renders nothing at the Suspense boundary. Based on the PR description, the component itself handles its own loading state. Consider revising for clarity:

- * comes back missing the entire projects section. The fallback renders- * the SSR skeleton; client takes over on hydration.+ * comes back missing the entire projects section. The null fallback+ * allows the component to render its own skeleton during client hydration.
🤖 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 `@app/hackathons/`[id]/page.tsx around lines 542 - 543, Update the inline
comment to accurately describe behavior at the Suspense boundary: state that
fallback={null} causes the Suspense to render nothing during fallback, and
clarify that the component (the child inside Suspense) is responsible for
rendering its own loading/SSR skeleton on hydration rather than the Suspense
boundary doing it; reference the Suspense usage and the fallback={null} prop so
reviewers can locate and revise the comment.
🤖 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.
Nitpick comments:
In `@app/hackathons/`[id]/page.tsx:
- Around line 542-543: Update the inline comment to accurately describe behavior
at the Suspense boundary: state that fallback={null} causes the Suspense to
render nothing during fallback, and clarify that the component (the child inside
Suspense) is responsible for rendering its own loading/SSR skeleton on hydration
rather than the Suspense boundary doing it; reference the Suspense usage and the
fallback={null} prop so reviewers can locate and revise the comment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 698aea5c-1f12-421a-8764-b1e4c689a7b7

📥 Commits

Reviewing files that changed from the base of the PR and between df9b0dc and edd6f8d.

📒 Files selected for processing (1)
  • app/hackathons/[id]/page.tsx

@agustinkassis
agustinkassis merged commit 07f827e into mainMay 12, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the fix/hackathons-prerender-truncation branch May 12, 2026 23:49
Sign up for freeto 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.

1 participant

@agustinkassis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender - #7

Merged
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation
May 12, 2026
Merged

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender#7
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation

Conversation

@agustinkassis

@agustinkassisagustinkassis commented May 12, 2026

Copy link
Copy Markdown
Member

Summary

On prod (https://lacrypta.dev/hackathons/commerce and every other hackathon page), the entire <HackathonProjectsList> section is missing from the rendered HTML. Users see the hero, calendar, sponsors, rules — and then nothing where the project list and links should be. On /hackathons/commerce this is especially visible: the page is Nostr-only (no curated projects-commerce.json), so the missing list means zero project links to click.

Diagnosis

Confirmed by comparing local SSR vs prod:

size"PROYECTOS INSCRIPTOS" in HTML"Rescanear Nostr" in HTML
Local dev102KB
Prod (Vercel)55KB

In the prod RSC stream, the page's main row references $L32 where the projects-list output should be, but row 32's data is never sent. The stream truncates right after the Participación rules card and jumps straight to metadata. Same shape on /hackathons/foundations.

Why specifically this slot:

  • app/hackathons/[id]/page.tsx is wrapped in "use cache" (line 284).
  • Inside, three client components are rendered: HackathonInscripcionButton (in hero), HackathonResultsClient (in awards card), and HackathonProjectsList (last child).
  • The first two render fine. Only the trailing one — which has the most hooks (useState, useRef, useMemo×3, useHackathonResults, useEffect) and the most transitive imports — gets dropped.

With Next 16 cacheComponents: true, prerendering a complex client component as a direct child of a "use cache" server function hits a streaming truncation: the prerender abandons the slot mid-stream and the rest of the body never arrives.

Fix

Wrap <HackathonProjectsList> in <Suspense fallback={null}>. This marks the slot as an explicit deferred streaming boundary, so the cached prerender stops trying to resolve it inline. Fallback is null (the component renders its own skeleton when nostrSubmissions === []). Client hydration takes over normally.

+ <Suspense fallback={null}>
<HackathonProjectsList hackathon={hackathon} />
+ </Suspense>

12-line diff to a single file, no behavioral change elsewhere.

Test plan

  • Vercel preview at <preview>/hackathons/commerce shows the project list with 6 Nostr submissions and clickable links.
  • curl -s <preview>/hackathons/commerce | wc -c ≈ 100KB+, not ~55KB.
  • curl -s <preview>/hackathons/commerce | grep -c 'Rescanear Nostr' ≥ 1.
  • Same checks on /hackathons/foundations (had the same truncation, just less visible because curated projects masked it).
  • Clicking any project link from /hackathons/commerce lands on /hackathons/commerce/<id> with the project page content.
  • Hard-reload the project page itself (existing path that did work) — still works, no regression.

Fallback if preview still truncates

If for some reason the Suspense wrap alone isn't sufficient on Vercel, the surgical follow-up is to move <HackathonProjectsList> out of the "use cache" boundary entirely — split the page into a cached sub-component (hero + cards) and a non-cached parent that renders both the cached part and the projects list. That keeps the cards prerendered statically while letting the dynamic list render uncached. Easy to apply as a second commit if needed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Optimized the hackathon projects section to load asynchronously, improving overall page loading experience.

Review Change Stack

…erender
On prod (Vercel), the `/hackathons/[id]` page is served with the entire
HackathonProjectsList section truncated from the RSC stream: the parent
row references $L32, but row 32's data is never sent. The page ends at
the "Participación" rules card and the projects list (with the project
links users want to click) simply doesn't render.
Local dev SSR includes the section fine (~102KB vs 55KB on prod).
The truncation is specific to the prerender path that combines a
`"use cache"` directive on the page with a complex client component
(useEffect + useMemo + useHackathonResults + lots of imports) as the
last child. Earlier client components on the page render fine
(HackathonInscripcionButton, HackathonResultsClient) — only the tail
gets dropped.
Wrapping `<HackathonProjectsList>` in `<Suspense fallback={null}>`
marks the slot as a deferred streaming boundary so the cached prerender
stops trying to resolve it inline. The fallback is null (the component
already renders its own loading skeleton when nostrSubmissions === []),
and client hydration takes over normally.
Verified locally: SSR HTML still contains "PROYECTOS INSCRIPTOS" /
"Rescanear Nostr" / "Inscribite gratis" after the change, and the
client component mounts correctly. The Vercel-side fix needs a preview
deploy to confirm row 32 actually arrives in the prod RSC stream now.
@vercel

vercelBot commented May 12, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreview, CommentMay 12, 2026 11:46pm

Request Review

@coderabbitai

coderabbitaiBot commented May 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds React Suspense support to the hackathon detail page by importing the Suspense component and wrapping the HackathonProjectsList section with a <Suspense fallback={null}> boundary. This defers rendering of the projects/leaderboard section, enabling streaming without blocking the parent page render.

Changes

Suspense boundary for projects streaming

Layer / File(s)Summary
Suspense boundary integration
app/hackathons/[id]/page.tsx
Add Suspense import from React and wrap HackathonProjectsList with <Suspense fallback={null}>, allowing the projects/leaderboard section to load asynchronously.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 A Suspense boundary, light and clean,
Lets projects load where they've never been,
No blocking streams, just async grace,
The hackathon finds its streaming pace! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and concisely identifies the main change: wrapping HackathonProjectsList in Suspense to fix a prerender issue. It accurately reflects the primary fix in the changeset.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hackathons-prerender-truncation

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/hackathons/[id]/page.tsx (1)

542-543: ⚡ Quick win

Clarify the comment about the fallback.

The comment states "The fallback renders the SSR skeleton" but fallback={null} renders nothing at the Suspense boundary. Based on the PR description, the component itself handles its own loading state. Consider revising for clarity:

- * comes back missing the entire projects section. The fallback renders- * the SSR skeleton; client takes over on hydration.+ * comes back missing the entire projects section. The null fallback+ * allows the component to render its own skeleton during client hydration.
🤖 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 `@app/hackathons/`[id]/page.tsx around lines 542 - 543, Update the inline
comment to accurately describe behavior at the Suspense boundary: state that
fallback={null} causes the Suspense to render nothing during fallback, and
clarify that the component (the child inside Suspense) is responsible for
rendering its own loading/SSR skeleton on hydration rather than the Suspense
boundary doing it; reference the Suspense usage and the fallback={null} prop so
reviewers can locate and revise the comment.
🤖 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.
Nitpick comments:
In `@app/hackathons/`[id]/page.tsx:
- Around line 542-543: Update the inline comment to accurately describe behavior
at the Suspense boundary: state that fallback={null} causes the Suspense to
render nothing during fallback, and clarify that the component (the child inside
Suspense) is responsible for rendering its own loading/SSR skeleton on hydration
rather than the Suspense boundary doing it; reference the Suspense usage and the
fallback={null} prop so reviewers can locate and revise the comment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 698aea5c-1f12-421a-8764-b1e4c689a7b7

📥 Commits

Reviewing files that changed from the base of the PR and between df9b0dc and edd6f8d.

📒 Files selected for processing (1)
  • app/hackathons/[id]/page.tsx

@agustinkassis
agustinkassis merged commit 07f827e into mainMay 12, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the fix/hackathons-prerender-truncation branch May 12, 2026 23:49
Sign up for freeto 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.

1 participant

@agustinkassis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender - #7

Merged
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation
May 12, 2026
Merged

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender#7
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation

Conversation

@agustinkassis

@agustinkassisagustinkassis commented May 12, 2026

Copy link
Copy Markdown
Member

Summary

On prod (https://lacrypta.dev/hackathons/commerce and every other hackathon page), the entire <HackathonProjectsList> section is missing from the rendered HTML. Users see the hero, calendar, sponsors, rules — and then nothing where the project list and links should be. On /hackathons/commerce this is especially visible: the page is Nostr-only (no curated projects-commerce.json), so the missing list means zero project links to click.

Diagnosis

Confirmed by comparing local SSR vs prod:

size"PROYECTOS INSCRIPTOS" in HTML"Rescanear Nostr" in HTML
Local dev102KB
Prod (Vercel)55KB

In the prod RSC stream, the page's main row references $L32 where the projects-list output should be, but row 32's data is never sent. The stream truncates right after the Participación rules card and jumps straight to metadata. Same shape on /hackathons/foundations.

Why specifically this slot:

  • app/hackathons/[id]/page.tsx is wrapped in "use cache" (line 284).
  • Inside, three client components are rendered: HackathonInscripcionButton (in hero), HackathonResultsClient (in awards card), and HackathonProjectsList (last child).
  • The first two render fine. Only the trailing one — which has the most hooks (useState, useRef, useMemo×3, useHackathonResults, useEffect) and the most transitive imports — gets dropped.

With Next 16 cacheComponents: true, prerendering a complex client component as a direct child of a "use cache" server function hits a streaming truncation: the prerender abandons the slot mid-stream and the rest of the body never arrives.

Fix

Wrap <HackathonProjectsList> in <Suspense fallback={null}>. This marks the slot as an explicit deferred streaming boundary, so the cached prerender stops trying to resolve it inline. Fallback is null (the component renders its own skeleton when nostrSubmissions === []). Client hydration takes over normally.

+ <Suspense fallback={null}>
<HackathonProjectsList hackathon={hackathon} />
+ </Suspense>

12-line diff to a single file, no behavioral change elsewhere.

Test plan

  • Vercel preview at <preview>/hackathons/commerce shows the project list with 6 Nostr submissions and clickable links.
  • curl -s <preview>/hackathons/commerce | wc -c ≈ 100KB+, not ~55KB.
  • curl -s <preview>/hackathons/commerce | grep -c 'Rescanear Nostr' ≥ 1.
  • Same checks on /hackathons/foundations (had the same truncation, just less visible because curated projects masked it).
  • Clicking any project link from /hackathons/commerce lands on /hackathons/commerce/<id> with the project page content.
  • Hard-reload the project page itself (existing path that did work) — still works, no regression.

Fallback if preview still truncates

If for some reason the Suspense wrap alone isn't sufficient on Vercel, the surgical follow-up is to move <HackathonProjectsList> out of the "use cache" boundary entirely — split the page into a cached sub-component (hero + cards) and a non-cached parent that renders both the cached part and the projects list. That keeps the cards prerendered statically while letting the dynamic list render uncached. Easy to apply as a second commit if needed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Optimized the hackathon projects section to load asynchronously, improving overall page loading experience.

Review Change Stack

…erender
On prod (Vercel), the `/hackathons/[id]` page is served with the entire
HackathonProjectsList section truncated from the RSC stream: the parent
row references $L32, but row 32's data is never sent. The page ends at
the "Participación" rules card and the projects list (with the project
links users want to click) simply doesn't render.
Local dev SSR includes the section fine (~102KB vs 55KB on prod).
The truncation is specific to the prerender path that combines a
`"use cache"` directive on the page with a complex client component
(useEffect + useMemo + useHackathonResults + lots of imports) as the
last child. Earlier client components on the page render fine
(HackathonInscripcionButton, HackathonResultsClient) — only the tail
gets dropped.
Wrapping `<HackathonProjectsList>` in `<Suspense fallback={null}>`
marks the slot as a deferred streaming boundary so the cached prerender
stops trying to resolve it inline. The fallback is null (the component
already renders its own loading skeleton when nostrSubmissions === []),
and client hydration takes over normally.
Verified locally: SSR HTML still contains "PROYECTOS INSCRIPTOS" /
"Rescanear Nostr" / "Inscribite gratis" after the change, and the
client component mounts correctly. The Vercel-side fix needs a preview
deploy to confirm row 32 actually arrives in the prod RSC stream now.
@vercel

vercelBot commented May 12, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreview, CommentMay 12, 2026 11:46pm

Request Review

@coderabbitai

coderabbitaiBot commented May 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds React Suspense support to the hackathon detail page by importing the Suspense component and wrapping the HackathonProjectsList section with a <Suspense fallback={null}> boundary. This defers rendering of the projects/leaderboard section, enabling streaming without blocking the parent page render.

Changes

Suspense boundary for projects streaming

Layer / File(s)Summary
Suspense boundary integration
app/hackathons/[id]/page.tsx
Add Suspense import from React and wrap HackathonProjectsList with <Suspense fallback={null}>, allowing the projects/leaderboard section to load asynchronously.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 A Suspense boundary, light and clean,
Lets projects load where they've never been,
No blocking streams, just async grace,
The hackathon finds its streaming pace! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and concisely identifies the main change: wrapping HackathonProjectsList in Suspense to fix a prerender issue. It accurately reflects the primary fix in the changeset.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hackathons-prerender-truncation

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/hackathons/[id]/page.tsx (1)

542-543: ⚡ Quick win

Clarify the comment about the fallback.

The comment states "The fallback renders the SSR skeleton" but fallback={null} renders nothing at the Suspense boundary. Based on the PR description, the component itself handles its own loading state. Consider revising for clarity:

- * comes back missing the entire projects section. The fallback renders- * the SSR skeleton; client takes over on hydration.+ * comes back missing the entire projects section. The null fallback+ * allows the component to render its own skeleton during client hydration.
🤖 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 `@app/hackathons/`[id]/page.tsx around lines 542 - 543, Update the inline
comment to accurately describe behavior at the Suspense boundary: state that
fallback={null} causes the Suspense to render nothing during fallback, and
clarify that the component (the child inside Suspense) is responsible for
rendering its own loading/SSR skeleton on hydration rather than the Suspense
boundary doing it; reference the Suspense usage and the fallback={null} prop so
reviewers can locate and revise the comment.
🤖 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.
Nitpick comments:
In `@app/hackathons/`[id]/page.tsx:
- Around line 542-543: Update the inline comment to accurately describe behavior
at the Suspense boundary: state that fallback={null} causes the Suspense to
render nothing during fallback, and clarify that the component (the child inside
Suspense) is responsible for rendering its own loading/SSR skeleton on hydration
rather than the Suspense boundary doing it; reference the Suspense usage and the
fallback={null} prop so reviewers can locate and revise the comment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 698aea5c-1f12-421a-8764-b1e4c689a7b7

📥 Commits

Reviewing files that changed from the base of the PR and between df9b0dc and edd6f8d.

📒 Files selected for processing (1)
  • app/hackathons/[id]/page.tsx

@agustinkassis
agustinkassis merged commit 07f827e into mainMay 12, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the fix/hackathons-prerender-truncation branch May 12, 2026 23:49
Sign up for freeto 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.

1 participant

@agustinkassis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender - #7

Merged
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation
May 12, 2026
Merged

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender#7
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation

Conversation

@agustinkassis

@agustinkassisagustinkassis commented May 12, 2026

Copy link
Copy Markdown
Member

Summary

On prod (https://lacrypta.dev/hackathons/commerce and every other hackathon page), the entire <HackathonProjectsList> section is missing from the rendered HTML. Users see the hero, calendar, sponsors, rules — and then nothing where the project list and links should be. On /hackathons/commerce this is especially visible: the page is Nostr-only (no curated projects-commerce.json), so the missing list means zero project links to click.

Diagnosis

Confirmed by comparing local SSR vs prod:

size"PROYECTOS INSCRIPTOS" in HTML"Rescanear Nostr" in HTML
Local dev102KB
Prod (Vercel)55KB

In the prod RSC stream, the page's main row references $L32 where the projects-list output should be, but row 32's data is never sent. The stream truncates right after the Participación rules card and jumps straight to metadata. Same shape on /hackathons/foundations.

Why specifically this slot:

  • app/hackathons/[id]/page.tsx is wrapped in "use cache" (line 284).
  • Inside, three client components are rendered: HackathonInscripcionButton (in hero), HackathonResultsClient (in awards card), and HackathonProjectsList (last child).
  • The first two render fine. Only the trailing one — which has the most hooks (useState, useRef, useMemo×3, useHackathonResults, useEffect) and the most transitive imports — gets dropped.

With Next 16 cacheComponents: true, prerendering a complex client component as a direct child of a "use cache" server function hits a streaming truncation: the prerender abandons the slot mid-stream and the rest of the body never arrives.

Fix

Wrap <HackathonProjectsList> in <Suspense fallback={null}>. This marks the slot as an explicit deferred streaming boundary, so the cached prerender stops trying to resolve it inline. Fallback is null (the component renders its own skeleton when nostrSubmissions === []). Client hydration takes over normally.

+ <Suspense fallback={null}>
<HackathonProjectsList hackathon={hackathon} />
+ </Suspense>

12-line diff to a single file, no behavioral change elsewhere.

Test plan

  • Vercel preview at <preview>/hackathons/commerce shows the project list with 6 Nostr submissions and clickable links.
  • curl -s <preview>/hackathons/commerce | wc -c ≈ 100KB+, not ~55KB.
  • curl -s <preview>/hackathons/commerce | grep -c 'Rescanear Nostr' ≥ 1.
  • Same checks on /hackathons/foundations (had the same truncation, just less visible because curated projects masked it).
  • Clicking any project link from /hackathons/commerce lands on /hackathons/commerce/<id> with the project page content.
  • Hard-reload the project page itself (existing path that did work) — still works, no regression.

Fallback if preview still truncates

If for some reason the Suspense wrap alone isn't sufficient on Vercel, the surgical follow-up is to move <HackathonProjectsList> out of the "use cache" boundary entirely — split the page into a cached sub-component (hero + cards) and a non-cached parent that renders both the cached part and the projects list. That keeps the cards prerendered statically while letting the dynamic list render uncached. Easy to apply as a second commit if needed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Optimized the hackathon projects section to load asynchronously, improving overall page loading experience.

Review Change Stack

…erender
On prod (Vercel), the `/hackathons/[id]` page is served with the entire
HackathonProjectsList section truncated from the RSC stream: the parent
row references $L32, but row 32's data is never sent. The page ends at
the "Participación" rules card and the projects list (with the project
links users want to click) simply doesn't render.
Local dev SSR includes the section fine (~102KB vs 55KB on prod).
The truncation is specific to the prerender path that combines a
`"use cache"` directive on the page with a complex client component
(useEffect + useMemo + useHackathonResults + lots of imports) as the
last child. Earlier client components on the page render fine
(HackathonInscripcionButton, HackathonResultsClient) — only the tail
gets dropped.
Wrapping `<HackathonProjectsList>` in `<Suspense fallback={null}>`
marks the slot as a deferred streaming boundary so the cached prerender
stops trying to resolve it inline. The fallback is null (the component
already renders its own loading skeleton when nostrSubmissions === []),
and client hydration takes over normally.
Verified locally: SSR HTML still contains "PROYECTOS INSCRIPTOS" /
"Rescanear Nostr" / "Inscribite gratis" after the change, and the
client component mounts correctly. The Vercel-side fix needs a preview
deploy to confirm row 32 actually arrives in the prod RSC stream now.
@vercel

vercelBot commented May 12, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreview, CommentMay 12, 2026 11:46pm

Request Review

@coderabbitai

coderabbitaiBot commented May 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds React Suspense support to the hackathon detail page by importing the Suspense component and wrapping the HackathonProjectsList section with a <Suspense fallback={null}> boundary. This defers rendering of the projects/leaderboard section, enabling streaming without blocking the parent page render.

Changes

Suspense boundary for projects streaming

Layer / File(s)Summary
Suspense boundary integration
app/hackathons/[id]/page.tsx
Add Suspense import from React and wrap HackathonProjectsList with <Suspense fallback={null}>, allowing the projects/leaderboard section to load asynchronously.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 A Suspense boundary, light and clean,
Lets projects load where they've never been,
No blocking streams, just async grace,
The hackathon finds its streaming pace! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and concisely identifies the main change: wrapping HackathonProjectsList in Suspense to fix a prerender issue. It accurately reflects the primary fix in the changeset.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hackathons-prerender-truncation

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/hackathons/[id]/page.tsx (1)

542-543: ⚡ Quick win

Clarify the comment about the fallback.

The comment states "The fallback renders the SSR skeleton" but fallback={null} renders nothing at the Suspense boundary. Based on the PR description, the component itself handles its own loading state. Consider revising for clarity:

- * comes back missing the entire projects section. The fallback renders- * the SSR skeleton; client takes over on hydration.+ * comes back missing the entire projects section. The null fallback+ * allows the component to render its own skeleton during client hydration.
🤖 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 `@app/hackathons/`[id]/page.tsx around lines 542 - 543, Update the inline
comment to accurately describe behavior at the Suspense boundary: state that
fallback={null} causes the Suspense to render nothing during fallback, and
clarify that the component (the child inside Suspense) is responsible for
rendering its own loading/SSR skeleton on hydration rather than the Suspense
boundary doing it; reference the Suspense usage and the fallback={null} prop so
reviewers can locate and revise the comment.
🤖 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.
Nitpick comments:
In `@app/hackathons/`[id]/page.tsx:
- Around line 542-543: Update the inline comment to accurately describe behavior
at the Suspense boundary: state that fallback={null} causes the Suspense to
render nothing during fallback, and clarify that the component (the child inside
Suspense) is responsible for rendering its own loading/SSR skeleton on hydration
rather than the Suspense boundary doing it; reference the Suspense usage and the
fallback={null} prop so reviewers can locate and revise the comment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 698aea5c-1f12-421a-8764-b1e4c689a7b7

📥 Commits

Reviewing files that changed from the base of the PR and between df9b0dc and edd6f8d.

📒 Files selected for processing (1)
  • app/hackathons/[id]/page.tsx

@agustinkassis
agustinkassis merged commit 07f827e into mainMay 12, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the fix/hackathons-prerender-truncation branch May 12, 2026 23:49
Sign up for freeto 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.

1 participant

@agustinkassis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender - #7

Merged
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation
May 12, 2026
Merged

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender#7
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation

Conversation

@agustinkassis

@agustinkassisagustinkassis commented May 12, 2026

Copy link
Copy Markdown
Member

Summary

On prod (https://lacrypta.dev/hackathons/commerce and every other hackathon page), the entire <HackathonProjectsList> section is missing from the rendered HTML. Users see the hero, calendar, sponsors, rules — and then nothing where the project list and links should be. On /hackathons/commerce this is especially visible: the page is Nostr-only (no curated projects-commerce.json), so the missing list means zero project links to click.

Diagnosis

Confirmed by comparing local SSR vs prod:

size"PROYECTOS INSCRIPTOS" in HTML"Rescanear Nostr" in HTML
Local dev102KB
Prod (Vercel)55KB

In the prod RSC stream, the page's main row references $L32 where the projects-list output should be, but row 32's data is never sent. The stream truncates right after the Participación rules card and jumps straight to metadata. Same shape on /hackathons/foundations.

Why specifically this slot:

  • app/hackathons/[id]/page.tsx is wrapped in "use cache" (line 284).
  • Inside, three client components are rendered: HackathonInscripcionButton (in hero), HackathonResultsClient (in awards card), and HackathonProjectsList (last child).
  • The first two render fine. Only the trailing one — which has the most hooks (useState, useRef, useMemo×3, useHackathonResults, useEffect) and the most transitive imports — gets dropped.

With Next 16 cacheComponents: true, prerendering a complex client component as a direct child of a "use cache" server function hits a streaming truncation: the prerender abandons the slot mid-stream and the rest of the body never arrives.

Fix

Wrap <HackathonProjectsList> in <Suspense fallback={null}>. This marks the slot as an explicit deferred streaming boundary, so the cached prerender stops trying to resolve it inline. Fallback is null (the component renders its own skeleton when nostrSubmissions === []). Client hydration takes over normally.

+ <Suspense fallback={null}>
<HackathonProjectsList hackathon={hackathon} />
+ </Suspense>

12-line diff to a single file, no behavioral change elsewhere.

Test plan

  • Vercel preview at <preview>/hackathons/commerce shows the project list with 6 Nostr submissions and clickable links.
  • curl -s <preview>/hackathons/commerce | wc -c ≈ 100KB+, not ~55KB.
  • curl -s <preview>/hackathons/commerce | grep -c 'Rescanear Nostr' ≥ 1.
  • Same checks on /hackathons/foundations (had the same truncation, just less visible because curated projects masked it).
  • Clicking any project link from /hackathons/commerce lands on /hackathons/commerce/<id> with the project page content.
  • Hard-reload the project page itself (existing path that did work) — still works, no regression.

Fallback if preview still truncates

If for some reason the Suspense wrap alone isn't sufficient on Vercel, the surgical follow-up is to move <HackathonProjectsList> out of the "use cache" boundary entirely — split the page into a cached sub-component (hero + cards) and a non-cached parent that renders both the cached part and the projects list. That keeps the cards prerendered statically while letting the dynamic list render uncached. Easy to apply as a second commit if needed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Optimized the hackathon projects section to load asynchronously, improving overall page loading experience.

Review Change Stack

…erender
On prod (Vercel), the `/hackathons/[id]` page is served with the entire
HackathonProjectsList section truncated from the RSC stream: the parent
row references $L32, but row 32's data is never sent. The page ends at
the "Participación" rules card and the projects list (with the project
links users want to click) simply doesn't render.
Local dev SSR includes the section fine (~102KB vs 55KB on prod).
The truncation is specific to the prerender path that combines a
`"use cache"` directive on the page with a complex client component
(useEffect + useMemo + useHackathonResults + lots of imports) as the
last child. Earlier client components on the page render fine
(HackathonInscripcionButton, HackathonResultsClient) — only the tail
gets dropped.
Wrapping `<HackathonProjectsList>` in `<Suspense fallback={null}>`
marks the slot as a deferred streaming boundary so the cached prerender
stops trying to resolve it inline. The fallback is null (the component
already renders its own loading skeleton when nostrSubmissions === []),
and client hydration takes over normally.
Verified locally: SSR HTML still contains "PROYECTOS INSCRIPTOS" /
"Rescanear Nostr" / "Inscribite gratis" after the change, and the
client component mounts correctly. The Vercel-side fix needs a preview
deploy to confirm row 32 actually arrives in the prod RSC stream now.
@vercel

vercelBot commented May 12, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreview, CommentMay 12, 2026 11:46pm

Request Review

@coderabbitai

coderabbitaiBot commented May 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds React Suspense support to the hackathon detail page by importing the Suspense component and wrapping the HackathonProjectsList section with a <Suspense fallback={null}> boundary. This defers rendering of the projects/leaderboard section, enabling streaming without blocking the parent page render.

Changes

Suspense boundary for projects streaming

Layer / File(s)Summary
Suspense boundary integration
app/hackathons/[id]/page.tsx
Add Suspense import from React and wrap HackathonProjectsList with <Suspense fallback={null}>, allowing the projects/leaderboard section to load asynchronously.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 A Suspense boundary, light and clean,
Lets projects load where they've never been,
No blocking streams, just async grace,
The hackathon finds its streaming pace! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and concisely identifies the main change: wrapping HackathonProjectsList in Suspense to fix a prerender issue. It accurately reflects the primary fix in the changeset.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hackathons-prerender-truncation

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/hackathons/[id]/page.tsx (1)

542-543: ⚡ Quick win

Clarify the comment about the fallback.

The comment states "The fallback renders the SSR skeleton" but fallback={null} renders nothing at the Suspense boundary. Based on the PR description, the component itself handles its own loading state. Consider revising for clarity:

- * comes back missing the entire projects section. The fallback renders- * the SSR skeleton; client takes over on hydration.+ * comes back missing the entire projects section. The null fallback+ * allows the component to render its own skeleton during client hydration.
🤖 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 `@app/hackathons/`[id]/page.tsx around lines 542 - 543, Update the inline
comment to accurately describe behavior at the Suspense boundary: state that
fallback={null} causes the Suspense to render nothing during fallback, and
clarify that the component (the child inside Suspense) is responsible for
rendering its own loading/SSR skeleton on hydration rather than the Suspense
boundary doing it; reference the Suspense usage and the fallback={null} prop so
reviewers can locate and revise the comment.
🤖 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.
Nitpick comments:
In `@app/hackathons/`[id]/page.tsx:
- Around line 542-543: Update the inline comment to accurately describe behavior
at the Suspense boundary: state that fallback={null} causes the Suspense to
render nothing during fallback, and clarify that the component (the child inside
Suspense) is responsible for rendering its own loading/SSR skeleton on hydration
rather than the Suspense boundary doing it; reference the Suspense usage and the
fallback={null} prop so reviewers can locate and revise the comment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 698aea5c-1f12-421a-8764-b1e4c689a7b7

📥 Commits

Reviewing files that changed from the base of the PR and between df9b0dc and edd6f8d.

📒 Files selected for processing (1)
  • app/hackathons/[id]/page.tsx

@agustinkassis
agustinkassis merged commit 07f827e into mainMay 12, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the fix/hackathons-prerender-truncation branch May 12, 2026 23:49
Sign up for freeto 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.

1 participant

@agustinkassis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender - #7

Merged
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation
May 12, 2026
Merged

fix(hackathons): wrap HackathonProjectsList in Suspense to unblock prerender#7
agustinkassis merged 1 commit into
mainfrom
fix/hackathons-prerender-truncation

Conversation

@agustinkassis

@agustinkassisagustinkassis commented May 12, 2026

Copy link
Copy Markdown
Member

Summary

On prod (https://lacrypta.dev/hackathons/commerce and every other hackathon page), the entire <HackathonProjectsList> section is missing from the rendered HTML. Users see the hero, calendar, sponsors, rules — and then nothing where the project list and links should be. On /hackathons/commerce this is especially visible: the page is Nostr-only (no curated projects-commerce.json), so the missing list means zero project links to click.

Diagnosis

Confirmed by comparing local SSR vs prod:

size"PROYECTOS INSCRIPTOS" in HTML"Rescanear Nostr" in HTML
Local dev102KB
Prod (Vercel)55KB

In the prod RSC stream, the page's main row references $L32 where the projects-list output should be, but row 32's data is never sent. The stream truncates right after the Participación rules card and jumps straight to metadata. Same shape on /hackathons/foundations.

Why specifically this slot:

  • app/hackathons/[id]/page.tsx is wrapped in "use cache" (line 284).
  • Inside, three client components are rendered: HackathonInscripcionButton (in hero), HackathonResultsClient (in awards card), and HackathonProjectsList (last child).
  • The first two render fine. Only the trailing one — which has the most hooks (useState, useRef, useMemo×3, useHackathonResults, useEffect) and the most transitive imports — gets dropped.

With Next 16 cacheComponents: true, prerendering a complex client component as a direct child of a "use cache" server function hits a streaming truncation: the prerender abandons the slot mid-stream and the rest of the body never arrives.

Fix

Wrap <HackathonProjectsList> in <Suspense fallback={null}>. This marks the slot as an explicit deferred streaming boundary, so the cached prerender stops trying to resolve it inline. Fallback is null (the component renders its own skeleton when nostrSubmissions === []). Client hydration takes over normally.

+ <Suspense fallback={null}>
<HackathonProjectsList hackathon={hackathon} />
+ </Suspense>

12-line diff to a single file, no behavioral change elsewhere.

Test plan

  • Vercel preview at <preview>/hackathons/commerce shows the project list with 6 Nostr submissions and clickable links.
  • curl -s <preview>/hackathons/commerce | wc -c ≈ 100KB+, not ~55KB.
  • curl -s <preview>/hackathons/commerce | grep -c 'Rescanear Nostr' ≥ 1.
  • Same checks on /hackathons/foundations (had the same truncation, just less visible because curated projects masked it).
  • Clicking any project link from /hackathons/commerce lands on /hackathons/commerce/<id> with the project page content.
  • Hard-reload the project page itself (existing path that did work) — still works, no regression.

Fallback if preview still truncates

If for some reason the Suspense wrap alone isn't sufficient on Vercel, the surgical follow-up is to move <HackathonProjectsList> out of the "use cache" boundary entirely — split the page into a cached sub-component (hero + cards) and a non-cached parent that renders both the cached part and the projects list. That keeps the cards prerendered statically while letting the dynamic list render uncached. Easy to apply as a second commit if needed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Optimized the hackathon projects section to load asynchronously, improving overall page loading experience.

Review Change Stack

…erender
On prod (Vercel), the `/hackathons/[id]` page is served with the entire
HackathonProjectsList section truncated from the RSC stream: the parent
row references $L32, but row 32's data is never sent. The page ends at
the "Participación" rules card and the projects list (with the project
links users want to click) simply doesn't render.
Local dev SSR includes the section fine (~102KB vs 55KB on prod).
The truncation is specific to the prerender path that combines a
`"use cache"` directive on the page with a complex client component
(useEffect + useMemo + useHackathonResults + lots of imports) as the
last child. Earlier client components on the page render fine
(HackathonInscripcionButton, HackathonResultsClient) — only the tail
gets dropped.
Wrapping `<HackathonProjectsList>` in `<Suspense fallback={null}>`
marks the slot as a deferred streaming boundary so the cached prerender
stops trying to resolve it inline. The fallback is null (the component
already renders its own loading skeleton when nostrSubmissions === []),
and client hydration takes over normally.
Verified locally: SSR HTML still contains "PROYECTOS INSCRIPTOS" /
"Rescanear Nostr" / "Inscribite gratis" after the change, and the
client component mounts correctly. The Vercel-side fix needs a preview
deploy to confirm row 32 actually arrives in the prod RSC stream now.
@vercel

vercelBot commented May 12, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreview, CommentMay 12, 2026 11:46pm

Request Review

@coderabbitai

coderabbitaiBot commented May 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds React Suspense support to the hackathon detail page by importing the Suspense component and wrapping the HackathonProjectsList section with a <Suspense fallback={null}> boundary. This defers rendering of the projects/leaderboard section, enabling streaming without blocking the parent page render.

Changes

Suspense boundary for projects streaming

Layer / File(s)Summary
Suspense boundary integration
app/hackathons/[id]/page.tsx
Add Suspense import from React and wrap HackathonProjectsList with <Suspense fallback={null}>, allowing the projects/leaderboard section to load asynchronously.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 A Suspense boundary, light and clean,
Lets projects load where they've never been,
No blocking streams, just async grace,
The hackathon finds its streaming pace! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and concisely identifies the main change: wrapping HackathonProjectsList in Suspense to fix a prerender issue. It accurately reflects the primary fix in the changeset.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hackathons-prerender-truncation

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/hackathons/[id]/page.tsx (1)

542-543: ⚡ Quick win

Clarify the comment about the fallback.

The comment states "The fallback renders the SSR skeleton" but fallback={null} renders nothing at the Suspense boundary. Based on the PR description, the component itself handles its own loading state. Consider revising for clarity:

- * comes back missing the entire projects section. The fallback renders- * the SSR skeleton; client takes over on hydration.+ * comes back missing the entire projects section. The null fallback+ * allows the component to render its own skeleton during client hydration.
🤖 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 `@app/hackathons/`[id]/page.tsx around lines 542 - 543, Update the inline
comment to accurately describe behavior at the Suspense boundary: state that
fallback={null} causes the Suspense to render nothing during fallback, and
clarify that the component (the child inside Suspense) is responsible for
rendering its own loading/SSR skeleton on hydration rather than the Suspense
boundary doing it; reference the Suspense usage and the fallback={null} prop so
reviewers can locate and revise the comment.
🤖 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.
Nitpick comments:
In `@app/hackathons/`[id]/page.tsx:
- Around line 542-543: Update the inline comment to accurately describe behavior
at the Suspense boundary: state that fallback={null} causes the Suspense to
render nothing during fallback, and clarify that the component (the child inside
Suspense) is responsible for rendering its own loading/SSR skeleton on hydration
rather than the Suspense boundary doing it; reference the Suspense usage and the
fallback={null} prop so reviewers can locate and revise the comment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 698aea5c-1f12-421a-8764-b1e4c689a7b7

📥 Commits

Reviewing files that changed from the base of the PR and between df9b0dc and edd6f8d.

📒 Files selected for processing (1)
  • app/hackathons/[id]/page.tsx

@agustinkassis
agustinkassis merged commit 07f827e into mainMay 12, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the fix/hackathons-prerender-truncation branch May 12, 2026 23:49
Sign up for freeto 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.

1 participant

@agustinkassis