From 8f2a51af9d323eeb5ecb44ced01e9fd7c601b68b Mon Sep 17 00:00:00 2001 From: giles Date: Mon, 23 Mar 2026 16:20:58 +0000 Subject: [PATCH] Isomorphic hydration: skip re-render when server HTML present sx-mount now checks if the target element has children (server- rendered HTML). If so, skips the client re-render and only runs hydration (process-elements, hydrate-islands, hydrate-elements). This preserves server-rendered CSSX styling and avoids the flash of unstyled content that occurred when the client replaced the server HTML before re-rendering. Co-Authored-By: Claude Opus 4.6 (1M context) --- shared/static/scripts/sx-browser.js | 12 ++++-------- web/boot.sx | 23 +++++++++++++---------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index d9e0f40..1835da9 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -14,7 +14,7 @@ // ========================================================================= var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); - var SX_VERSION = "2026-03-23T15:39:32Z"; + var SX_VERSION = "2026-03-23T16:18:55Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -4627,16 +4627,12 @@ PRIMITIVES["hoist-head-elements-full"] = hoistHeadElementsFull; // sx-mount var sxMount = function(target, source, extraEnv) { return (function() { var el = resolveMountTarget(target); - return (isSxTruthy(el) ? (function() { + return (isSxTruthy(el) ? ((isSxTruthy(isEmpty(domChildList(el))) ? (function() { var node = sxRenderWithEnv(source, extraEnv); domSetTextContent(el, ""); domAppend(el, node); - hoistHeadElementsFull(el); - processElements(el); - sxHydrateElements(el); - sxHydrateIslands(el); - return runPostRenderHooks(); -})() : NIL); + return hoistHeadElementsFull(el); +})() : NIL), processElements(el), sxHydrateElements(el), sxHydrateIslands(el), runPostRenderHooks()) : NIL); })(); }; PRIMITIVES["sx-mount"] = sxMount; diff --git a/web/boot.sx b/web/boot.sx index 50ac1e4..023d0a4 100644 --- a/web/boot.sx +++ b/web/boot.sx @@ -79,16 +79,19 @@ ;; extra-env: optional extra bindings dict (let ((el (resolve-mount-target target))) (when el - (let ((node (sx-render-with-env source extra-env))) - (dom-set-text-content el "") - (dom-append el node) - ;; Hoist head elements from rendered content - (hoist-head-elements-full el) - ;; Process sx- attributes, hydrate data-sx and islands - (process-elements el) - (sx-hydrate-elements el) - (sx-hydrate-islands el) - (run-post-render-hooks)))))) + ;; If the server already rendered content (isomorphic SSR), + ;; skip re-render — just hydrate the existing DOM. + (when (empty? (dom-child-list el)) + (let ((node (sx-render-with-env source extra-env))) + (dom-set-text-content el "") + (dom-append el node) + ;; Hoist head elements from rendered content + (hoist-head-elements-full el))) + ;; Process sx- attributes, hydrate data-sx and islands + (process-elements el) + (sx-hydrate-elements el) + (sx-hydrate-islands el) + (run-post-render-hooks))))) ;; --------------------------------------------------------------------------