Skip to content

fix: render link safety modal in a portal to avoid <p> hydration error#529

Open
osipov-anton wants to merge 1 commit into
vercel:mainfrom
osipov-anton:fix/link-modal-hydration-portal
Open

fix: render link safety modal in a portal to avoid <p> hydration error#529
osipov-anton wants to merge 1 commit into
vercel:mainfrom
osipov-anton:fix/link-modal-hydration-portal

Conversation

@osipov-anton
Copy link
Copy Markdown

Summary

When linkSafety is enabled, the link component renders a <button> alongside the LinkSafetyModal. The modal's root is a <div> rendered inline, but markdown links live inside paragraph <p> elements — so the modal <div> ends up as a descendant of <p>.

This produces the React hydration error:

In HTML, <div> cannot be a descendant of <p>. This will cause a hydration error.

Fix

Render the default LinkSafetyModal through createPortal to document.body, so the modal <div> is no longer nested inside the paragraph. The inline <button> (valid inside <p>) stays where it is. The component also guards against SSR (typeof document === "undefined").

Test plan

  • pnpm --filter streamdown test — all 982 tests pass
  • Updated link-modal-keyboard, link-safety, and translations tests to query the portaled DOM (document / document.body) instead of the render container
  • Manually verify no hydration warning when an external link appears inside a paragraph with link safety enabled

Made with Cursor

The link safety modal rendered its backdrop <div> inline next to the
link <button>. Since markdown links live inside paragraph <p> elements,
the modal <div> became a descendant of <p>, triggering the React
hydration error "In HTML, <div> cannot be a descendant of <p>".

Render the modal through createPortal to document.body so it is no
longer nested inside the paragraph.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 31, 2026

@osipov-anton is attempting to deploy a commit to the Vercel Team on Vercel.

A member of the Team first needs to authorize it.

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