fix: make prerender cache work, including in dev (#14860)
authorRich Harris <richard.a.harris@gmail.com>
Thu, 30 Oct 2025 17:09:49 +0000 (18:09 +0100)
committerGitHub <noreply@github.com>
Thu, 30 Oct 2025 17:09:49 +0000 (18:09 +0100)
* fix: make prerender cache work

* populate cache on startup

* lint

.changeset/tender-rings-hammer.md [new file with mode: 0644]
packages/kit/src/core/sync/write_client_manifest.js
packages/kit/src/runtime/client/remote-functions/prerender.svelte.js
packages/kit/src/runtime/client/remote-functions/query.svelte.js
packages/kit/src/runtime/client/remote-functions/shared.svelte.js
packages/kit/src/runtime/client/types.d.ts

diff --git a/.changeset/tender-rings-hammer.md b/.changeset/tender-rings-hammer.md
new file mode 100644 (file)
index 0000000..67ff89d
--- /dev/null
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: make prerender cache work, including in development
index c27c61add43c80f5ab092cf488872f175379c232..7495e3134854f75f960aa46885a0f394d17e8a78 100644 (file)
@@ -171,6 +171,7 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
                        };
 
                        export const decoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.decode]));
+                       export const encoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.encode]));
 
                        export const hash = ${s(kit.router.type === 'hash')};
 
index ad00d2757276f50b3306d4908754ed90d968f2c0..4f622605eee99c09b725243f70d6d16ec8b48579 100644 (file)
@@ -1,4 +1,3 @@
-/** @import { RemoteFunctionResponse } from 'types' */
 import { app_dir, base } from '$app/paths/internal/client';
 import { version } from '__sveltekit/environment';
 import * as devalue from 'devalue';
@@ -7,12 +6,12 @@ import { app, remote_responses } from '../client.js';
 import { create_remote_function, remote_request } from './shared.svelte.js';
 
 // Initialize Cache API for prerender functions
-const CACHE_NAME = `sveltekit:${version}`;
+const CACHE_NAME = DEV ? `sveltekit:${Date.now()}` : `sveltekit:${version}`;
 /** @type {Cache | undefined} */
 let prerender_cache;
 
-void (async () => {
-       if (!DEV && typeof caches !== 'undefined') {
+const prerender_cache_ready = (async () => {
+       if (typeof caches !== 'undefined') {
                try {
                        prerender_cache = await caches.open(CACHE_NAME);
 
@@ -111,53 +110,68 @@ class Prerender {
        }
 }
 
+/**
+ * @param {string} url
+ * @param {string} encoded
+ */
+function put(url, encoded) {
+       return /** @type {Cache} */ (prerender_cache)
+               .put(
+                       url,
+                       // We need to create a new response because the original response is already consumed
+                       new Response(encoded, {
+                               headers: {
+                                       'Content-Type': 'application/json'
+                               }
+                       })
+               )
+               .catch(() => {
+                       // Nothing we can do here
+               });
+}
+
 /**
  * @param {string} id
  */
 export function prerender(id) {
        return create_remote_function(id, (cache_key, payload) => {
                return new Prerender(async () => {
-                       if (Object.hasOwn(remote_responses, cache_key)) {
-                               return remote_responses[cache_key];
-                       }
+                       await prerender_cache_ready;
 
                        const url = `${base}/${app_dir}/remote/${id}${payload ? `/${payload}` : ''}`;
 
+                       if (Object.hasOwn(remote_responses, cache_key)) {
+                               const data = remote_responses[cache_key];
+
+                               if (prerender_cache) {
+                                       void put(url, devalue.stringify(data, app.encoders));
+                               }
+
+                               return data;
+                       }
+
                        // Check the Cache API first
                        if (prerender_cache) {
                                try {
                                        const cached_response = await prerender_cache.match(url);
+
                                        if (cached_response) {
-                                               const cached_result = /** @type { RemoteFunctionResponse & { type: 'result' } } */ (
-                                                       await cached_response.json()
-                                               );
-                                               return devalue.parse(cached_result.result, app.decoders);
+                                               const cached_result = await cached_response.text();
+                                               return devalue.parse(cached_result, app.decoders);
                                        }
                                } catch {
                                        // Nothing we can do here
                                }
                        }
 
-                       const result = await remote_request(url);
+                       const encoded = await remote_request(url);
 
                        // For successful prerender requests, save to cache
                        if (prerender_cache) {
-                               try {
-                                       await prerender_cache.put(
-                                               url,
-                                               // We need to create a new response because the original response is already consumed
-                                               new Response(JSON.stringify(result), {
-                                                       headers: {
-                                                               'Content-Type': 'application/json'
-                                                       }
-                                               })
-                                       );
-                               } catch {
-                                       // Nothing we can do here
-                               }
+                               void put(url, encoded);
                        }
 
-                       return result;
+                       return devalue.parse(encoded, app.decoders);
                });
        });
 }
index 1f4d5b7db5373d8178f8c10931c7310898d9863d..d8569a49249b132b593c0285ee57e6d102b6bf79 100644 (file)
@@ -31,7 +31,8 @@ export function query(id) {
 
                        const url = `${base}/${app_dir}/remote/${id}${payload ? `?payload=${payload}` : ''}`;
 
-                       return await remote_request(url);
+                       const result = await remote_request(url);
+                       return devalue.parse(result, app.decoders);
                });
        });
 }
index 7595ebd655495316a441d098838ce8e91114a030..05f9fdb84171ba308c8a3ab4030e5ff241ec424b 100644 (file)
@@ -37,7 +37,7 @@ export async function remote_request(url) {
                throw new HttpError(result.status ?? 500, result.error);
        }
 
-       return devalue.parse(result.result, app.decoders);
+       return result.result;
 }
 
 /**
index e1956c47f19aac89065175b04d4885c2b85addb2..927b5b7a1041c93f75ddaf3b9901f9b7c77c2598 100644 (file)
@@ -50,6 +50,7 @@ export interface SvelteKitApp {
        decode: (type: string, value: any) => any;
 
        decoders: Record<string, (data: any) => any>;
+       encoders: Record<string, (data: any) => any>;
 
        /**
         * Whether or not we're using hash-based routing