Skip to content

Hotfix: SSR HTML rendering issue on dev mode - #240

Open
MarcosProWork wants to merge 1 commit into
VolvoxCommunity:mainfrom
MarcosProWork:hotfix/dev-SSR-HTML-rendering
Open

Hotfix: SSR HTML rendering issue on dev mode#240
MarcosProWork wants to merge 1 commit into
VolvoxCommunity:mainfrom
MarcosProWork:hotfix/dev-SSR-HTML-rendering

Conversation

@MarcosProWork

Copy link
Copy Markdown

Summary

Fixes a React hydration mismatch thrown by the theme-toggle button in the site navigation.

useTheme() from next-themes returns resolvedTheme === undefined during SSR - the active theme lives in localStorage and isn't knowable until the client script runs. Both toggle buttons branched on it at render time:

{resolvedTheme === "light" ? <Moon /> : <Sun />}

So the server always took the Sun branch, while the client's first render resolved the real theme and produced Moon. Same element, different <path d=...> → hydration mismatch.

A tree hydrated but some attributes of the server rendered HTML didn't match
the client properties. This won't be patched up.

Fix

Render both icons unconditionally and let the .dark class on <html> decide which is visible. next-themes sets that class via a pre-paint inline script, so the correct icon is showing before first paint — no flash, and the server and client HTML are byte-identical.

<Moon weight="fill" className="h-5 w-5 block dark:hidden" />
<Sun  weight="fill" className="h-5 w-5 hidden dark:block" />

This is the same SSR-safe pattern already used by ThemeToggle in src/components/theme-toggle.tsx and by the light/dark logo swap in src/components/products/product-hero.tsx — this PR brings the two remaining holdouts in line with it.

toggleTheme still reads resolvedTheme, which is correct: it only runs on click, long after mount.

Changes

  • src/components/navigation.tsx — the component in the reported stack trace. CSS-based icon swap.
  • src/components/blog/blog-post-header.tsx — same latent bug, plus its aria-label branched on resolvedTheme too (an attribute-level mismatch on top of the element one); now a static "Toggle theme". Note this component currently has no importers, so the fix is pre-emptive.

Notes

  • Despite the branch name, this is not dev-only. Production SSR hits the same mismatch; React just logs a terser message there instead of the full diff.
  • src/components/ui/sonner.tsx also reads resolvedTheme, but Sonner's Toaster renders nothing server-side, so it is not a mismatch source. Left alone.

Verification

  • pnpm typecheck — clean
  • pnpm lint — no new findings in the touched files (pre-existing warnings elsewhere are unrelated)
  • Served HTML from the dev server now contains both icon paths with block dark:hidden / hidden dark:block, confirming the server output no longer depends on resolvedTheme
  • Console hydration error no longer reproduces on load

@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

@MarcosProWork is attempting to deploy a commit to the Volvox Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Accessibility

    • Improved theme toggle labeling for more consistent assistive technology support.
  • User Interface

    • Updated theme toggle icons to switch reliably between light and dark modes.
    • Improved visual consistency across blog post headers and navigation.

Walkthrough

Changes

Theme toggle rendering

Layer / File(s) Summary
Theme toggle icon rendering
src/components/blog/blog-post-header.tsx, src/components/navigation.tsx
Both theme toggles render Moon and Sun icons at the same time. CSS dark-mode classes control icon visibility. The blog post header uses a stable aria-label.

Suggested reviewers: rabden

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the SSR rendering issue addressed by the theme-toggle changes, although the issue also affects production.
Description check ✅ Passed The description directly explains the hydration mismatch, the CSS-based icon fix, affected files, and verification results.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@coderabbitai
coderabbitai Bot requested a review from rabden August 5, 2026 18:35
@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR removes render-time dependence on resolvedTheme from two theme-toggle buttons to keep their server and client markup consistent.

  • Renders both theme icons and controls visibility with dark-mode utility classes.
  • Replaces the blog header’s theme-dependent accessible label with a stable label.
  • Does not add regression coverage for the changed SSR and accessibility behavior.

Confidence Score: 4/5

The PR appears safe to merge, with the non-blocking concern that its hydration regression fix and label change are not protected by tests.

The dual-icon markup removes the server/client render branch as intended, but future regressions in SSR output, icon visibility, and accessible labeling would not be detected by the current test suite.

Files Needing Attention: src/components/navigation.tsx; src/components/blog/blog-post-header.tsx

Important Files Changed

Filename Overview
src/components/navigation.tsx Adopts an SSR-stable dual-icon theme toggle, but the changed rendering behavior lacks regression coverage.
src/components/blog/blog-post-header.tsx Adopts the same dual-icon strategy and stabilizes the accessible label without updating tests.

Fix All in Codex

Prompt To Fix All With AI
### Issue 1
src/components/navigation.tsx:266-267
**Theme behavior lacks regression coverage**

The new dual-icon SSR strategy and the related label change in `blog-post-header.tsx` have no corresponding tests, so regressions in server markup, theme-based icon visibility, or the accessible label will pass the test suite undetected.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "Hotfix: SSR HTML rendering issue on dev ..." | Re-trigger Greptile

Comment on lines +266 to +267
<Moon weight="fill" className="h-5 w-5 block dark:hidden" />
<Sun weight="fill" className="h-5 w-5 hidden dark:block" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Theme behavior lacks regression coverage

The new dual-icon SSR strategy and the related label change in blog-post-header.tsx have no corresponding tests, so regressions in server markup, theme-based icon visibility, or the accessible label will pass the test suite undetected.

Rule Used: What: Require adding/updating/deleting tests to ma... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/navigation.tsx
Line: 266-267

Comment:
**Theme behavior lacks regression coverage**

The new dual-icon SSR strategy and the related label change in `blog-post-header.tsx` have no corresponding tests, so regressions in server markup, theme-based icon visibility, or the accessible label will pass the test suite undetected.

**Rule Used:** What: Require adding/updating/deleting tests to ma... ([source](https://app.greptile.com/volvox/-/custom-context?memory=d6acce3d-3556-4bfc-b39f-f945c8c677e6))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@src/components/blog/blog-post-header.tsx`:
- Around line 61-66: Add data-testid="theme-toggle" to the Button wrapping the
theme-toggle icons in the blog post header, while preserving its existing
aria-label and icon rendering.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 48bbd43e-a111-43e1-a4cd-e4ca8f9ae07c

📥 Commits

Reviewing files that changed from the base of the PR and between f35b56e and 7d7633a.

📒 Files selected for processing (2)
  • src/components/blog/blog-post-header.tsx
  • src/components/navigation.tsx

Comment on lines +61 to +66
aria-label="Toggle theme"
>
{resolvedTheme === "light" ? (
<Moon weight="fill" className="h-5 w-5" />
) : (
<Sun weight="fill" className="h-5 w-5" />
)}
{/* Both icons render on the server; the `.dark` class decides
which is visible, so SSR and hydration always match. */}
<Moon weight="fill" className="h-5 w-5 block dark:hidden" />
<Sun weight="fill" className="h-5 w-5 hidden dark:block" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the required test ID to this theme toggle.

This Button is a key theme-toggle element, but it does not expose data-testid="theme-toggle". Add the test ID so SSR and hydration regression tests can target this instance consistently.

As per coding guidelines, src/**/*.tsx requires data-testid attributes for key elements, including theme-toggle.

Proposed fix
                 aria-label="Toggle theme"
+                data-testid="theme-toggle"
📝 Committable suggestion

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

Suggested change
aria-label="Toggle theme"
>
{resolvedTheme === "light" ? (
<Moon weight="fill" className="h-5 w-5" />
) : (
<Sun weight="fill" className="h-5 w-5" />
)}
{/* Both icons render on the server; the `.dark` class decides
which is visible, so SSR and hydration always match. */}
<Moon weight="fill" className="h-5 w-5 block dark:hidden" />
<Sun weight="fill" className="h-5 w-5 hidden dark:block" />
aria-label="Toggle theme"
data-testid="theme-toggle"
>
{/* Both icons render on the server; the `.dark` class decides
which is visible, so SSR and hydration always match. */}
<Moon weight="fill" className="h-5 w-5 block dark:hidden" />
<Sun weight="fill" className="h-5 w-5 hidden dark:block" />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/blog/blog-post-header.tsx` around lines 61 - 66, Add
data-testid="theme-toggle" to the Button wrapping the theme-toggle icons in the
blog post header, while preserving its existing aria-label and icon rendering.

Source: Coding guidelines

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant