Skip to content

fix(OUT-3643): avoid editor hydration error - #202

Closed
priosshrsth wants to merge 2 commits into
mainfrom
anit/out-3643-hydration-error
Closed

fix(OUT-3643): avoid editor hydration error#202
priosshrsth wants to merge 2 commits into
mainfrom
anit/out-3643-hydration-error

Conversation

@priosshrsth

Copy link
Copy Markdown
Collaborator

What changed

  • ClientEditorWrapper now gates its render on a client mounted flag, so the editor only renders after hydration — fixes the hydration mismatch from server/client backgroundColor differences.
  • mise.toml: added a dev task that runs the dev server via portless.

What to review

  • Confirm the mounted gate doesn't introduce a visible flash/layout shift on first paint (returns null until mounted).
  • Verify SSR-rendered content vs client output no longer mismatches.

🤖 Generated with Claude Code

…ation error

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@linear-code

linear-code Bot commented Jun 1, 2026

Copy link
Copy Markdown

OUT-3643

@vercel

vercel Bot commented Jun 1, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
client-home-v3 Ready Ready Preview, Comment Jun 12, 2026 11:14am

Request Review

@greptile-apps

greptile-apps Bot commented Jun 1, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes hydration mismatches in ClientEditorWrapper by returning null until after client-side mount, and adds a dev task to mise.toml for running the dev server via portless.

  • ClientEditorWrapper.tsx: Introduces a mounted guard (useState + useEffect) so the component renders nothing on the server and during the initial hydration pass, then renders the full editor tree once the client useEffect fires. All existing hooks are called unconditionally before the early return, satisfying the Rules of Hooks.
  • mise.toml: Adds a [tasks.\"dev\"] entry that runs portless client-home bun dev; no functional code change.

Confidence Score: 5/5

Safe to merge — the change correctly defers editor rendering until after client hydration, and all hook calls remain unconditional before the early return.

The mounted guard is a well-understood pattern for suppressing hydration mismatches in Next.js client components. All hooks are called in the same order on every render before the conditional return. The blank-until-mounted behaviour was acknowledged as intentional by the author. No logic errors, missing guards, or data correctness issues were found.

No files require special attention.

Important Files Changed

Filename Overview
src/features/editor/components/ClientEditorWrapper.tsx Adds a standard mounted guard to suppress hydration mismatches; all hooks are called unconditionally before the early return, satisfying the Rules of Hooks. No logic errors found.
mise.toml Adds a dev task running portless for local development; no functional concerns.

Reviews (2): Last reviewed commit: "Update src/features/editor/components/Cl..." | Re-trigger Greptile

Comment on lines +51 to +53
if (!mounted) {
return null
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Guaranteed blank flash on every page load

Because this is a 'use client' component that Next.js still SSR-renders, the server also runs the component with mounted = false and emits null into the initial HTML. On the client, the first hydration pass also returns null (matching the server), so there is no mismatch — but that means the entire editor area is absent from the page until the useEffect fires post-hydration. Every visitor on every load will see a blank space where the editor should be, which is a layout shift regardless of connection speed.

A narrower fix is to add suppressHydrationWarning directly on the <div> that carries the dynamic backgroundColor, which silences the mismatch only for that attribute while keeping the SSR-rendered markup visible. Alternatively, wrapping the import in dynamic(() => import('./ClientEditorWrapper'), { ssr: false }) at the call site explicitly opts out of SSR and makes the blank-until-mounted behaviour intentional and contained.

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!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@greptileai the issue is there are lots of places where hydration mismatch. So it is not a good idea to patch all these doms and also ensure future changes do not bring the issue again.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

That's a fair point — if the editor has many sources of hydration mismatch (dynamic colors, user preferences, theme state, etc.), patching each DOM node is not scalable and won't guard against future regressions.

The concern with the current approach isn't the strategy, it's that SSR still emits null into the HTML (since the server runs this component too and hits mounted = false). The blank flash is unavoidable with this pattern.

The fix that achieves your goal — skip SSR for the whole editor, centralized in one place — is dynamic with ssr: false at the call site:

// wherever ClientEditorWrapper is imported
const ClientEditorWrapper = dynamic(
  () => import('./ClientEditorWrapper').then(m => m.ClientEditorWrapper),
  { ssr: false }
)

Then you can remove the mounted gate entirely. The result is identical behavior (blank until JS hydrates), but:

  • The intent is explicit and colocated with the import
  • No SSR output means no hydration reconciliation attempt at all — all future mismatches inside the component are also ignored
  • The component itself stays clean

The blank flash exists in both approaches, but ssr: false makes it deliberate and contained rather than an emergent side effect of the mounted check.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

Comment thread src/features/editor/components/ClientEditorWrapper.tsx Outdated
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
@priosshrsth

Copy link
Copy Markdown
Collaborator Author

@greptileai re review the pr again.

@SandipBajracharya SandipBajracharya left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants