Repository navigation
Hydration from server side rendered content - #1472
Merged
Merged
Conversation
|
✅ Issues Benchmark: No regressions or improvements — performance is 1:1. |
|
✅ SSR Benchmark: No regressions or improvements — performance and memory are 1:1. |
⚡ Nordcraft Runtime Performance Benchmark
|
jacob-kofoed
commented
Oct 30, 2026
jacob-kofoed
commented
Oct 30, 2026
jacob-kofoed
commented
Oct 30, 2026
jacob-kofoed
commented
Oct 30, 2026
jacob-kofoed
commented
Oct 30, 2026
jacob-kofoed
commented
Oct 30, 2026
jacob-kofoed
commented
Oct 30, 2026
jacob-kofoed
commented
Oct 30, 2026
jacob-kofoed
commented
Oct 30, 2026
jacob-kofoed
force-pushed
the
hydrate-rc
branch
from
October 30, 2026 09:39
fc09727 to
e260dc1
Compare
jacob-kofoed
marked this pull request as ready for review
October 30, 2026 10:30
erik-beus
requested changes
Oct 30, 2026
Co-authored-by: Erik Beuschau <32451595+erik-beus@users.noreply.github.com>
There was a problem hiding this comment.
🟡 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
Preserve slot-depth disambiguators for duplicate slot names · New Clear unanchored option text before rebuilding children · New Merge generated classes after initializing attributes · New Add unconditional static classes to adopted elements · New Restore textarea value when adopting SSR elements · New Insert fallback children in forward order to preserve script execution · New Preserve per-parent sibling scan progress during hydration · New Claim SSR rows through unmarked tbody wrappers · New Reconcile adopted and fallback roots in forward order · New
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
erik-beus
approved these changes
Nov 2, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

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
isServerformula, 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.