From a3a577abfe9367e42fdef0704de31e424a2e4076 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Fri, 21 Aug 2026 21:58:23 +0200 Subject: [PATCH] perf(router-core): cache static SSR manifest serialization in LRU --- RESULT-perf-task3.md | 123 +++++++++ packages/router-core/src/ssr/ssr-server.ts | 179 +++++++++++-- .../tests/ssr-manifest-dehydrate.perf.test.ts | 155 +++++++++++ .../tests/ssr-server-manifest-cache.test.ts | 247 ++++++++++++++++++ 4 files changed, 688 insertions(+), 16 deletions(-) create mode 100644 RESULT-perf-task3.md create mode 100644 packages/router-core/tests/ssr-manifest-dehydrate.perf.test.ts create mode 100644 packages/router-core/tests/ssr-server-manifest-cache.test.ts diff --git a/RESULT-perf-task3.md b/RESULT-perf-task3.md new file mode 100644 index 00000000000..cb4d97adced --- /dev/null +++ b/RESULT-perf-task3.md @@ -0,0 +1,123 @@ +# RESULT — perf/task3-ssr-manifest-cache + +## Goal + +`packages/router-core/src/ssr/ssr-server.ts` `dehydrate()` re-serializes the static +route manifest (matched-route asset descriptors) through seroval on **every** +request, although it is byte-identical for a given matched-route set. This work +caches the _serialized_ manifest fragment in an LRU and emits it as a separate +script assignment so only dynamic data goes through the per-request stream. + +## Cross-reference analysis (task 1) — why splitting is safe here + +Seroval's `crossSerializeStream` builds one reference-ID graph (`$R["tsr"]`) +per stream call and deduplicates shared object identity inside that graph. +Naive splitting would be unsafe **only if** objects were shared across the +manifest/match boundary — then one side would reference an ID the other side +never defines, breaking hydration. + +Analysis of the data: + +- The manifest fragment (`preparedManifest.routes`, `scriptFormat`, + inline-CSS placeholder) is produced by the bundler plugin at build time: + plain JSON-safe data (strings, arrays, plain objects, booleans). +- The per-request part (`matches` → `dehydrateMatch()`: loaderData, + beforeLoadContext, errors; plus optional request-scoped assets) is created + independently at runtime by loaders/user code. +- No code path assigns a manifest route object into match data or vice versa; + the two trees never share references (request assets are merged via + `{...spread}` copies into a fresh root-route object, + `mergeRequestAssetsIntoRootRoute`). + +Conclusion: no cross-references exist between the two portions, so splitting is +hydration-safe. Additionally, the split halves are emitted into the _same_ +inline `