Skip to content

Hydration from server side rendered content - #1472

Merged
jacob-kofoed merged 8 commits into
mainfrom
hydrate-rc
Nov 2, 2026
Merged

jacob-kofoed merged 8 commits into
mainfrom
hydrate-rc

Conversation

@jacob-kofoed

@jacob-kofoed jacob-kofoed commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Replaces the full client-side DOM rebuild on initial page load with SSR hydration.

The most important feature for hydration to work is stable and deterministic data-id so the runtime can walk and adopt the existing server-rendered DOM nodes while only attaching signal subscriptions, event listeners, etc. This was almost working after we have worked on it last few months, but I managed to move it to a shared file in core to keep them even more in sync.

When SSR and client state diverge, due to isServer formula, custom-code formulas, or client-side API responses, or just bugs in how we set data-ids (🤞 ) hydration falls back per node. Missing elements are created and inserted in the expected child order, and any leftover SSR nodes with data-id that were not adopted during the initial pass are swept from the DOM once rendering finishes (while leaving foreign non-Nordcraft nodes untouched). So in theory no breaking changes to be found here 🤞

I considered rendering <script> tags during SSR, but this would be a breaking change, so I left it as today where they are not sent and only created on the client. This is handled as an expected miss during hydration.

This will require a lot of testing on as many projects as possible. I already tested editor and nordcraft, nordcraft-admin etc.

@github-actions

Copy link
Copy Markdown

✅ Issues Benchmark: No regressions or improvements — performance is 1:1.

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

✅ SSR Benchmark: No regressions or improvements — performance and memory are 1:1.

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

⚡ Nordcraft Runtime Performance Benchmark

  • Noise & Equivalence Threshold: ±1.5% (Delta within CI or threshold reported as 1:1)
  • Bootstrap: 1000 iterations, seed 0
  • Base ref: 9b39b5b
  • Page Bundle Identity: Distinct build artifacts
  • Runtime Size: Base 92.1 KB vs. Head 93.9 KB (Delta: +1.8 KB (+2.0%))
  • Runtime Size (compressed): Base 25.3 KB vs. Head 25.9 KB (Delta: +572 B (+2.2%))

✅ No regressions detected. Runtime performance is 1:1 or improved.

Scenario Base/Head time Delta Time Verdict Base/Head heap Delta Heap Verdict
mount-nordcraft 24.20 ms / 22.40 ms -7.44% (-1.80 ms) 🟢 1:1 3.92 MB / 4.02 MB +98.2 KB (+2.4%) 🟢 Stable
mount-nordcraft-no-hydrate 25.70 ms / 26.30 ms +2.33% (+0.60 ms) 🟢 1:1 4.03 MB / 3.90 MB -139.2 KB (-3.4%) 💾 -3.4% (-139.2 KB)
mount-pricing 5.20 ms / 4.60 ms -11.54% (-0.60 ms) 🚀 11.5% Faster 651.7 KB / 670.9 KB +19.2 KB (+2.9%) 🟢 1:1
reactive-clicks 11.20 ms / 11.30 ms +0.89% (+0.10 ms) 🟢 1:1 1.58 MB / 1.58 MB -0.8 KB (-0.0%) 🟢 1:1
list-repeat 17.60 ms / 17.10 ms -2.84% (-0.50 ms) 🟢 1:1 8.96 MB / 8.67 MB -294.2 KB (-3.2%) 💾 -3.2% (-294.2 KB)
custom-element 23.10 ms / 22.50 ms -2.60% (-0.60 ms) 🟢 1:1 13.48 MB / 13.40 MB -73.6 KB (-0.5%) 🟢 1:1
repeat-list-shuffle 18.20 ms / 17.30 ms -4.95% (-0.90 ms) 🚀 4.9% Faster 8.34 MB / 8.26 MB -78.8 KB (-0.9%) 🟢 1:1
lifecycle-churn 5.80 ms / 5.20 ms -10.34% (-0.60 ms) 🚀 10.3% Faster 1.80 MB / 1.74 MB -64.6 KB (-3.5%) 💾 -3.5% (-64.6 KB)
style-variables 19.00 ms / 18.30 ms -3.68% (-0.70 ms) 🟢 1:1 3.70 MB / 3.69 MB -13.6 KB (-0.4%) 🟢 1:1
context-propagation 11.60 ms / 11.60 ms +0.00% (+0.00 ms) 🟢 1:1 3.95 MB / 3.94 MB -12.8 KB (-0.3%) 🟢 1:1

Comment thread benchmarks/browser/harness.js Resolved
Comment thread packages/runtime/src/page.main.ts Resolved
@jacob-kofoed
jacob-kofoed marked this pull request as ready for review October 30, 2026 10:30

@erik-beus erik-beus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please see questions

jacob-kofoed and others added 3 commits October 30, 2026 14:59
Co-authored-by: Erik Beuschau <32451595+erik-beus@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved hydration and slot-ID defects can duplicate content, reorder nodes and script execution, lose textarea values and styling, and produce nonunique selectors.

9 open issues
What changed in this PR

This PR introduces initial-page hydration so the runtime can reuse server-rendered DOM instead of rebuilding it, using shared deterministic node IDs.

Changes:

  • Shares repeat and slot path helpers between SSR and the runtime.
  • Adds DOM adoption, fallback rendering, and stale-node cleanup while retaining client-only script creation.
  • Adds hydration and ordering tests, build flags, and hydration-versus-fresh-mount benchmarks.
File Description
packages/​ssr/​src/​rendering/​stableIds.test.ts Tests deterministic SSR IDs and reserved attributes.
packages/​ssr/​src/​rendering/​components.ts Uses shared paths and adds text-node IDs.
packages/​ssr/​src/​rendering/​components.test.ts Updates SSR expectations and request setup.
packages/​ssr/​src/​rendering/​attributes.ts Filters reserved runtime attributes.
packages/​runtime/​src/​page.main.ts Enables hydration and post-render cleanup.
packages/​runtime/​src/​happydom.ts Guards duplicate test DOM registration.
packages/​runtime/​src/​custom-element/​ToddleComponent.test.ts Initializes the test DOM environment.
packages/​runtime/​src/​components/​repeatOrdering.test.ts Tests repeat and slotted-child ordering.
packages/​runtime/​src/​components/​renderComponent.ts Passes hydration state into rendering.
packages/​runtime/​src/​components/​hydrationRoundtrip.test.ts Tests adoption of SSR markup snapshots.
packages/​runtime/​src/​components/​hydration.ts Implements node claiming and stale-node cleanup.
packages/​runtime/​src/​components/​hydration.test.ts Covers hydration, updates, and fallback behavior.
packages/​runtime/​src/​components/​createText.ts Adopts SSR text wrappers.
packages/​runtime/​src/​components/​createText.test.ts Initializes the test DOM environment.
packages/​runtime/​src/​components/​createSlot.ts Shares slot suffix logic and forwards hydration state.
packages/​runtime/​src/​components/​createNode.ts Shares repeat paths and propagates hydration state.
packages/​runtime/​src/​components/​createElement.ts Adopts elements and reconciles their children.
packages/​runtime/​src/​components/​createComponent.ts Shares slotted paths and forwards hydration state.
packages/​runtime/​scripts/​bundleConfig.ts Defines the page-runtime build flag.
packages/​core/​src/​utils/​nodePath.ts Adds shared node-path helpers.
packages/​core/​src/​utils/​nodePath.test.ts Tests path and suffix conventions.
packages/​core/​src/​index.ts Exports node-path helpers.
packages/​core/​src/​compileTime.ts Declares the page flag and unbundled fallback.
bin/​runRuntimeBenchmark.ts Adds a fresh-mount comparison case.
bin/​prepareReleaseAssets.ts Configures page flags for release bundles.
benchmarks/​runtimeConfig.test.ts Checks the new benchmark configuration.
benchmarks/​browser/​harness.js Prepares DOM templates outside hydration timing.
.vscode/​settings.json Adds hydration terminology to the spelling dictionary.

Review effort: Balanced

Comment thread packages/core/src/utils/nodePath.ts Resolved
Comment thread packages/runtime/src/page.main.ts Resolved
@jacob-kofoed jacob-kofoed changed the title Hydration candidate Hydration from server side rendered content Nov 2, 2026
@jacob-kofoed
jacob-kofoed merged commit 32c11b0 into main Nov 2, 2026
16 checks passed
@jacob-kofoed
jacob-kofoed deleted the hydrate-rc branch November 2, 2026 11:21
@jacob-kofoed jacob-kofoed mentioned this pull request Nov 3, 2026
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.

3 participants