From 11d4a4b76f7fae83ef8b4a4ea1d305317b312b7e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 17 Feb 2022 11:31:13 -0500 Subject: [PATCH] Fall back to full page reload if link href doesn't match anything (#3969) * failing test for #3935 * fall back to full page navigation for unmatched routes - closes #3935 * changeset * fix initial spa render of 404 errors (#3980) Co-authored-by: mrkishi Co-authored-by: mrkishi --- .changeset/four-news-turn.md | 5 ++++ packages/kit/src/runtime/app/navigation.js | 4 ++-- packages/kit/src/runtime/client/renderer.js | 23 ++++++++++++------- packages/kit/src/runtime/client/router.js | 8 +++++-- packages/kit/src/runtime/client/types.d.ts | 1 + .../basics/src/routes/routing/index.svelte | 1 + packages/kit/test/apps/basics/test/test.js | 9 ++++++++ 7 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 .changeset/four-news-turn.md diff --git a/.changeset/four-news-turn.md b/.changeset/four-news-turn.md new file mode 100644 index 000000000000..b72e1c753eb8 --- /dev/null +++ b/.changeset/four-news-turn.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Fall back to full page reload if link href does not match route manifest diff --git a/packages/kit/src/runtime/app/navigation.js b/packages/kit/src/runtime/app/navigation.js index 52ee7d4397b3..6db75bb57610 100644 --- a/packages/kit/src/runtime/app/navigation.js +++ b/packages/kit/src/runtime/app/navigation.js @@ -45,8 +45,8 @@ async function invalidate_(resource) { /** * @type {import('$app/navigation').prefetch} */ -function prefetch_(href) { - return router.prefetch(new URL(href, get_base_uri(document))); +async function prefetch_(href) { + await router.prefetch(new URL(href, get_base_uri(document))); } /** diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 68146301cacd..91b2bc739c7d 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -149,7 +149,7 @@ export class Renderer { /** @type {Map} */ this.cache = new Map(); - /** @type {{id: string | null, promise: Promise | null}} */ + /** @type {{id: string | null, promise: Promise | null}} */ this.loading = { id: null, promise: null @@ -316,6 +316,11 @@ export class Renderer { const token = (this.token = {}); let navigation_result = await this._get_navigation_result(info, no_cache); + if (!navigation_result) { + location.href = info.url.href; + return; + } + // abort if user navigated during update if (token !== this.token) return; @@ -411,7 +416,7 @@ export class Renderer { /** * @param {import('./types').NavigationInfo} info - * @returns {Promise} + * @returns {Promise} */ load(info) { this.loading.promise = this._get_navigation_result(info, false); @@ -471,7 +476,7 @@ export class Renderer { /** * @param {import('./types').NavigationInfo} info * @param {boolean} no_cache - * @returns {Promise} + * @returns {Promise} */ async _get_navigation_result(info, no_cache) { if (this.loading.id === info.id && this.loading.promise) { @@ -504,11 +509,13 @@ export class Renderer { if (result) return result; } - return await this._load_error({ - status: 404, - error: new Error(`Not found: ${info.url.pathname}`), - url: info.url - }); + if (info.initial) { + return await this._load_error({ + status: 404, + error: new Error(`Not found: ${info.url.pathname}`), + url: info.url + }); + } } /** diff --git a/packages/kit/src/runtime/client/router.js b/packages/kit/src/runtime/client/router.js index e49a5e68fb2f..93100fa72bd9 100644 --- a/packages/kit/src/runtime/client/router.js +++ b/packages/kit/src/runtime/client/router.js @@ -66,6 +66,7 @@ export class Router { renderer.router = this; this.enabled = true; + this.initialized = false; // make it possible to reset focus document.body.setAttribute('tabindex', '-1'); @@ -257,6 +258,8 @@ export class Router { ); } }); + + this.initialized = true; } #update_scroll_positions() { @@ -283,7 +286,8 @@ export class Router { id: url.pathname + url.search, routes: this.routes.filter(([pattern]) => pattern.test(path)), url, - path + path, + initial: !this.initialized }; } } @@ -333,7 +337,7 @@ export class Router { /** * @param {URL} url - * @returns {Promise} + * @returns {Promise} */ async prefetch(url) { const info = this.parse(url); diff --git a/packages/kit/src/runtime/client/types.d.ts b/packages/kit/src/runtime/client/types.d.ts index 19a1a2744c5e..334399d8081c 100644 --- a/packages/kit/src/runtime/client/types.d.ts +++ b/packages/kit/src/runtime/client/types.d.ts @@ -5,6 +5,7 @@ export type NavigationInfo = { routes: CSRRoute[]; url: URL; path: string; + initial: boolean; }; export type NavigationCandidate = { diff --git a/packages/kit/test/apps/basics/src/routes/routing/index.svelte b/packages/kit/test/apps/basics/src/routes/routing/index.svelte index 44373a09cb9e..9db2b75ca481 100644 --- a/packages/kit/test/apps/basics/src/routes/routing/index.svelte +++ b/packages/kit/test/apps/basics/src/routes/routing/index.svelte @@ -7,5 +7,6 @@ a ok elsewhere +static.json
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index 7902bf0f8194..c83e450369fb 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -2112,6 +2112,15 @@ test.describe.parallel('Routing', () => { await page.goto('/routing/rest/complex/prefix-one/two/three'); expect(await page.textContent('h1')).toBe('parts: one/two/three'); }); + + test('links to unmatched routes result in a full page navigation, not a 404', async ({ + page, + clicknav + }) => { + await page.goto('/routing'); + await clicknav('[href="/static.json"]'); + expect(await page.textContent('body')).toBe('"static file"\n'); + }); }); test.describe.parallel('Session', () => {