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) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 16:20:58 +00:00
parent fa700e0202
commit 8f2a51af9d
2 changed files with 17 additions and 18 deletions

View File

@@ -14,7 +14,7 @@
// ========================================================================= // =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); 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 isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); } function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -4627,16 +4627,12 @@ PRIMITIVES["hoist-head-elements-full"] = hoistHeadElementsFull;
// sx-mount // sx-mount
var sxMount = function(target, source, extraEnv) { return (function() { var sxMount = function(target, source, extraEnv) { return (function() {
var el = resolveMountTarget(target); var el = resolveMountTarget(target);
return (isSxTruthy(el) ? (function() { return (isSxTruthy(el) ? ((isSxTruthy(isEmpty(domChildList(el))) ? (function() {
var node = sxRenderWithEnv(source, extraEnv); var node = sxRenderWithEnv(source, extraEnv);
domSetTextContent(el, ""); domSetTextContent(el, "");
domAppend(el, node); domAppend(el, node);
hoistHeadElementsFull(el); return hoistHeadElementsFull(el);
processElements(el); })() : NIL), processElements(el), sxHydrateElements(el), sxHydrateIslands(el), runPostRenderHooks()) : NIL);
sxHydrateElements(el);
sxHydrateIslands(el);
return runPostRenderHooks();
})() : NIL);
})(); }; })(); };
PRIMITIVES["sx-mount"] = sxMount; PRIMITIVES["sx-mount"] = sxMount;

View File

@@ -79,16 +79,19 @@
;; extra-env: optional extra bindings dict ;; extra-env: optional extra bindings dict
(let ((el (resolve-mount-target target))) (let ((el (resolve-mount-target target)))
(when el (when el
(let ((node (sx-render-with-env source extra-env))) ;; If the server already rendered content (isomorphic SSR),
(dom-set-text-content el "") ;; skip re-render — just hydrate the existing DOM.
(dom-append el node) (when (empty? (dom-child-list el))
;; Hoist head elements from rendered content (let ((node (sx-render-with-env source extra-env)))
(hoist-head-elements-full el) (dom-set-text-content el "")
;; Process sx- attributes, hydrate data-sx and islands (dom-append el node)
(process-elements el) ;; Hoist head elements from rendered content
(sx-hydrate-elements el) (hoist-head-elements-full el)))
(sx-hydrate-islands el) ;; Process sx- attributes, hydrate data-sx and islands
(run-post-render-hooks)))))) (process-elements el)
(sx-hydrate-elements el)
(sx-hydrate-islands el)
(run-post-render-hooks)))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------