diff --git a/.changeset/unlucky-knives-bake.md b/.changeset/unlucky-knives-bake.md new file mode 100644 index 000000000000..588a1b7f28a7 --- /dev/null +++ b/.changeset/unlucky-knives-bake.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: correctly show 404 for prerendered dynamic routes when navigating client-side without a root layout server load diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 80abcfd65742..b68f4bfbade4 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -55,6 +55,17 @@ function update_scroll_positions(index) { scroll_positions[index] = scroll_state(); } +/** + * Loads `href` the old-fashioned way, with a full page reload. + * Returns a `Promise` that never resolves (to prevent any + * subsequent work, e.g. history manipulation, from happening) + * @param {URL} url + */ +function native_navigation(url) { + location.href = url.href; + return new Promise(() => {}); +} + /** * @param {import('./types.js').SvelteKitApp} app * @param {HTMLElement} target @@ -1196,17 +1207,6 @@ export function create_client(app, target) { return await native_navigation(url); } - /** - * Loads `href` the old-fashioned way, with a full page reload. - * Returns a `Promise` that never resolves (to prevent any - * subsequent work, e.g. history manipulation, from happening) - * @param {URL} url - */ - function native_navigation(url) { - location.href = url.href; - return new Promise(() => {}); - } - if (import.meta.hot) { import.meta.hot.on('vite:beforeUpdate', () => { if (current.error) location.reload(); @@ -1842,7 +1842,7 @@ export function create_client(app, target) { /** * @param {URL} url * @param {boolean[]} invalid - * @returns {Promise} + * @returns {Promise} */ async function load_data(url, invalid) { const data_url = new URL(url); @@ -1857,13 +1857,19 @@ async function load_data(url, invalid) { const res = await native_fetch(data_url.href); + // if `__data.json` doesn't exist or the server has an internal error, + // fallback to native navigation so we avoid parsing the HTML error page as a JSON + if (res.headers.get('content-type')?.includes('text/html')) { + await native_navigation(url); + } + if (!res.ok) { // error message is a JSON-stringified string which devalue can't handle at the top level // turn it into a HttpError to not call handleError on the client again (was already handled on the server) throw new HttpError(res.status, await res.json()); } - // TODO: fix eslint error + // TODO: fix eslint error / figure out if it actually applies to our situation // eslint-disable-next-line return new Promise(async (resolve) => { /**