From 5b0dbb5e6ea89a50df29bd0ad3e5238b516a77f3 Mon Sep 17 00:00:00 2001 From: Rami Bitar Date: Tue, 30 Jun 2026 16:11:22 -0400 Subject: [PATCH] Fix dialog SSR crash by gating DialogPortal to client DialogPortal aliased Radix's Portal directly, which calls createPortal(children, document.body) during render. Static prerendering (e.g. /about via InquiryDialog) has no document global and threw "document is not defined". Wrap it so the portal only mounts client-side. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_012Exmd6Wn13TGp1sArgK7y4 --- components/ui/dialog.tsx | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx index b2f1b98..78b84e3 100644 --- a/components/ui/dialog.tsx +++ b/components/ui/dialog.tsx @@ -10,7 +10,26 @@ const Dialog = DialogPrimitive.Root; const DialogTrigger = DialogPrimitive.Trigger; -const DialogPortal = DialogPrimitive.Portal; +// Only render the portal on the client. During SSR/static prerendering the +// `document` global doesn't exist, and React Dom's createPortal(children, +// document.body) inside Radix's Portal would throw "document is not defined". +const DialogPortal = ({ + children, + ...props +}: React.ComponentPropsWithoutRef) => { + const [mounted, setMounted] = React.useState(false); + + React.useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) { + return null; + } + + return {children}; +}; +DialogPortal.displayName = DialogPrimitive.Portal.displayName; const DialogClose = DialogPrimitive.Close;