From 08b502b308d3f174ac9995388f19243672f9bd50 Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Fri, 5 Dec 2025 16:05:13 -0700 Subject: [PATCH 1/3] fix: Don't crash on `hydratable` serialization failures --- .changeset/two-lizards-poke.md | 5 +++++ packages/svelte/src/internal/server/hydratable.js | 8 +++++--- 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 .changeset/two-lizards-poke.md diff --git a/.changeset/two-lizards-poke.md b/.changeset/two-lizards-poke.md new file mode 100644 index 000000000000..b94a93b3fa77 --- /dev/null +++ b/.changeset/two-lizards-poke.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: Don't crash on hydratable serialization failure diff --git a/packages/svelte/src/internal/server/hydratable.js b/packages/svelte/src/internal/server/hydratable.js index 5711885ed41c..6347022a453f 100644 --- a/packages/svelte/src/internal/server/hydratable.js +++ b/packages/svelte/src/internal/server/hydratable.js @@ -78,9 +78,11 @@ function encode(key, value, unresolved) { const placeholder = `"${uid++}"`; (entry.promises ??= []).push( - p.then((s) => { - entry.serialized = entry.serialized.replace(placeholder, s); - }) + p + .then((s) => { + entry.serialized = entry.serialized.replace(placeholder, s); + }) + .catch(() => {}) ); return placeholder; From 370beb4b0a838596e117ccd0dcdf989cce0de3a2 Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Fri, 5 Dec 2025 16:36:36 -0700 Subject: [PATCH 2/3] better --- .../svelte/src/internal/server/hydratable.js | 30 ++++++++----------- .../hydratable-unserializable/_config.js | 6 ++++ .../hydratable-unserializable/main.svelte | 5 ++++ 3 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 packages/svelte/tests/server-side-rendering/samples/hydratable-unserializable/_config.js create mode 100644 packages/svelte/tests/server-side-rendering/samples/hydratable-unserializable/main.svelte diff --git a/packages/svelte/src/internal/server/hydratable.js b/packages/svelte/src/internal/server/hydratable.js index 6347022a453f..81f59ab2fd5b 100644 --- a/packages/svelte/src/internal/server/hydratable.js +++ b/packages/svelte/src/internal/server/hydratable.js @@ -57,8 +57,16 @@ function encode(key, value, unresolved) { entry.serialized = devalue.uneval(entry.value, (value, uneval) => { if (is_promise(value)) { + // we serialize promises as `"${i}"`, because it's impossible for that string + // to occur 'naturally' (since the quote marks would have to be escaped) + // this placeholder is returned synchronously from `uneval`, which includes it in the + // serialized string. Later (at least one microtask from now), when `p.then` runs, it'll + // be replaced. + const placeholder = `"${uid++}"`; const p = value - .then((v) => `r(${uneval(v)})`) + .then((v) => { + entry.serialized = entry.serialized.replace(placeholder, `r(${uneval(v)})`); + }) .catch((devalue_error) => e.hydratable_serialization_failed( key, @@ -66,25 +74,11 @@ function encode(key, value, unresolved) { ) ); - // prevent unhandled rejections from crashing the server - p.catch(() => {}); - - // track which promises are still resolving when render is complete unresolved?.set(p, key); - p.finally(() => unresolved?.delete(p)); - - // we serialize promises as `"${i}"`, because it's impossible for that string - // to occur 'naturally' (since the quote marks would have to be escaped) - const placeholder = `"${uid++}"`; - - (entry.promises ??= []).push( - p - .then((s) => { - entry.serialized = entry.serialized.replace(placeholder, s); - }) - .catch(() => {}) - ); + // prevent unhandled rejections from crashing the server, track which promises are still resolving when render is complete + p.catch(() => {}).finally(() => unresolved?.delete(p)); + (entry.promises ??= []).push(p); return placeholder; } }); diff --git a/packages/svelte/tests/server-side-rendering/samples/hydratable-unserializable/_config.js b/packages/svelte/tests/server-side-rendering/samples/hydratable-unserializable/_config.js new file mode 100644 index 000000000000..79b0c90fe6b8 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/hydratable-unserializable/_config.js @@ -0,0 +1,6 @@ +import { test } from '../../test'; + +export default test({ + mode: ['async'], + error: 'hydratable_serialization_failed' +}); diff --git a/packages/svelte/tests/server-side-rendering/samples/hydratable-unserializable/main.svelte b/packages/svelte/tests/server-side-rendering/samples/hydratable-unserializable/main.svelte new file mode 100644 index 000000000000..e104dec12bf0 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/hydratable-unserializable/main.svelte @@ -0,0 +1,5 @@ + \ No newline at end of file From ac41805b56553550073c5616c559d1cb74f51158 Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Sat, 6 Dec 2025 12:28:06 -0700 Subject: [PATCH 3/3] Update two-lizards-poke.md Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- .changeset/two-lizards-poke.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/two-lizards-poke.md b/.changeset/two-lizards-poke.md index b94a93b3fa77..fecfbd482e1a 100644 --- a/.changeset/two-lizards-poke.md +++ b/.changeset/two-lizards-poke.md @@ -2,4 +2,4 @@ 'svelte': patch --- -fix: Don't crash on hydratable serialization failure +fix: don't crash on `hydratable` serialization failure