Repository: sveltejs/kit (JavaScript)

Issue:
Failure to fetch `/__data.json?x-sveltekit-invalidated` on "rerouted" pages in development
### Describe the bug

Following the new [reroute](https://kit.svelte.dev/docs/hooks#universal-hooks-reroute) hook from https://github.com/sveltejs/kit/pull/11537, invalidating load function(s) on a "rerouted" page will result in a 404 error for `__data.json?x-sveltekit-invalidated=...` (invoking `invalidate` or `invalidateAll` from `$app/navigation`).

Please note that this does NOT happen in build output from my test with `preview` script and `@sveltejs/adapter-static`.

---

For example, with the following `reroute` setup...

```javascript
// src/hooks.js

/** @type {Record<string, string>} */
const translated = {
	'/en': '/',
};

/** @type {import('@sveltejs/kit').Reroute} */
export function reroute({ url }) {
	console.log(`REROUTE`, url.toString());
	if (url.pathname in translated) {
		return translated[url.pathname];
	}
}
```

..., calling `invalidateAll` while on `/en` page will cause kit to throw the aforementioned 404...

```svelte
<!-- src/routes/+page.svelte -->
<script>
	import { invalidateAll } from '$app/navigation';
</script>

<button on:click={() => invalidateAll()}>Invalidate</button>
```

### Reproduction

Please follow the steps below:

1. Clone project at https://github.com/vnphanquang/sveltekit-reroute-invalidate-load-function-reproduction,
2. install dependencies,
3. start `dev` script,
4. go to `http://localhost:5173/en`,
5. click on `invalidate` button,
6. observe 404 on page and logs from both browser and terminal.

### Logs

```Shell
REROUTE http://localhost:5173/en/__data.json?x-sveltekit-invalidated=01
SvelteKitError: Not found: /en
    at resolve ([...truncated...]/test-reroute-hooks/node_modules/.pnpm/@sveltejs+kit@2.3.0_@sveltejs+vite-plugin-svelte@3.0.1_svelte@4.2.8_vite@5.0.11/node_modules/@sveltejs/kit/src/runtime/server/respond.js:495:13)
    at resolve ([...truncated...]/test-reroute-hooks/node_modules/.pnpm/@sveltejs+kit@2.3.0_@sveltejs+vite-plugin-svelte@3.0.1_svelte@4.2.8_vite@5.0.11/node_modules/@sveltejs/kit/src/runtime/server/respond.js:295:5)
    at #options.hooks.handle ([...truncated...]/test-reroute-hooks/node_modules/.pnpm/@sveltejs+kit@2.3.0_@sveltejs+vite-plugin-svelte@3.0.1_svelte@4.2.8_vite@5.0.11/node_modules/@sveltejs/kit/src/runtime/server/index.js:63:56)
    at Module.respond ([...truncated...]/test-reroute-hooks/node_modules/.pnpm/@sveltejs+kit@2.3.0_@sveltejs+vite-plugin-svelte@3.0.1_svelte@4.2.8_vite@5.0.11/node_modules/@sveltejs/kit/src/runtime/server/respond.js:292:40)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  status: 404,
  text: 'Not Found'
}
```


### System Info

```Shell
System:
    OS: Linux 6.6 Arch Linux
    CPU: (4) x64 Intel(R) Core(TM) i5-4590 CPU @ 3.30GHz
    Memory: 3.29 GB / 15.57 GB
    Container: Yes
    Shell: 3.7.0 - /usr/bin/fish
  Binaries:
    Node: 20.10.0 - ~/.volta/tools/image/node/20.10.0/bin/node
    npm: 10.2.3 - ~/.volta/tools/image/node/20.10.0/bin/npm
    pnpm: 8.10.0 - ~/.volta/bin/pnpm
  npmPackages:
    @sveltejs/adapter-auto: ^3.0.0 => 3.1.0 
    @sveltejs/adapter-static: ^3.0.1 => 3.0.1 
    @sveltejs/kit: ^2.3.2 => 2.3.2 
    @sveltejs/vite-plugin-svelte: ^3.0.0 => 3.0.1 
    svelte: ^4.2.7 => 4.2.8 
    vite: ^5.0.3 => 5.0.11
```


### Severity

annoyance

### Additional Information

This might be related to some [other open issues about `__data.json`](https://github.com/sveltejs/kit/issues?q=is%3Aissue+is%3Aopen+__data.json)


Discussion:
Workaround for now

```javascript
// src/hooks.js

/** @type {Record<string, string>} */
const translated = {
	'/en': '/',
};

/** @type {import('@sveltejs/kit').Reroute} */
export function reroute({ url }) {
	console.log(`REROUTE`, url.toString());

	let suffix = '';
	let pathname = url.pathname;
	const segments = pathname.split('/');
	const lastSegment = segments.at(-1);
	if (lastSegment && /\.\w+$/.test(lastSegment)) {
		suffix = '/' + lastSegment;
		pathname = segments.slice(0, -1).join('/');
	}

	if (pathname in translated) {
		return translated[pathname] + suffix;
	}
}
```
Thanks, @vnphanquang! I've been facing the same behaviour and wasn't sure what is going on. I also noticed `reroute` runs for every mouse move over link, not on enter only.
@alesvaupotic yeah if [link options](https://kit.svelte.dev/docs/link-options) is set up for preloading, hovering on links might trigger client-side fetching for page data, which should hit `reroute` hook.



Patch:
diff --git a/.changeset/curvy-trains-dress.md b/.changeset/curvy-trains-dress.md
new file mode 100644
index 000000000000..2d29ff1b47b2
--- /dev/null
+++ b/.changeset/curvy-trains-dress.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: strip internal data before passing URL to `reroute`
diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js
index e260a08b00f1..ab51505a897c 100644
--- a/packages/kit/src/runtime/server/respond.js
+++ b/packages/kit/src/runtime/server/respond.js
@@ -85,6 +85,21 @@ export async function respond(request, options, manifest, state) {
 		return text('Not found', { status: 404 });
 	}
 
+	const is_data_request = has_data_suffix(url.pathname);
+	/** @type {boolean[] | undefined} */
+	let invalidated_data_nodes;
+	if (is_data_request) {
+		url.pathname =
+			strip_data_suffix(url.pathname) +
+				(url.searchParams.get(TRAILING_SLASH_PARAM) === '1' ? '/' : '') || '/';
+		url.searchParams.delete(TRAILING_SLASH_PARAM);
+		invalidated_data_nodes = url.searchParams
+			.get(INVALIDATED_PARAM)
+			?.split('')
+			.map((node) => node === '1');
+		url.searchParams.delete(INVALIDATED_PARAM);
+	}
+
 	// reroute could alter the given URL, so we pass a copy
 	let rerouted_path;
 	try {
@@ -126,22 +141,6 @@ export async function respond(request, options, manifest, state) {
 		return text('Not found', { status: 404, headers });
 	}
 
-	const is_data_request = has_data_suffix(decoded);
-	/** @type {boolean[] | undefined} */
-	let invalidated_data_nodes;
-	if (is_data_request) {
-		decoded = strip_data_suffix(decoded) || '/';
-		url.pathname =
-			strip_data_suffix(url.pathname) +
-				(url.searchParams.get(TRAILING_SLASH_PARAM) === '1' ? '/' : '') || '/';
-		url.searchParams.delete(TRAILING_SLASH_PARAM);
-		invalidated_data_nodes = url.searchParams
-			.get(INVALIDATED_PARAM)
-			?.split('')
-			.map((node) => node === '1');
-		url.searchParams.delete(INVALIDATED_PARAM);
-	}
-
 	if (!state.prerendering?.fallback) {
 		// TODO this could theoretically break — should probably be inside a try-catch
 		const matchers = await manifest._.matchers();


Test patch:
diff --git a/packages/kit/test/apps/basics/src/hooks.js b/packages/kit/test/apps/basics/src/hooks.js
index deb1576401b0..3897bb949252 100644
--- a/packages/kit/test/apps/basics/src/hooks.js
+++ b/packages/kit/test/apps/basics/src/hooks.js
@@ -4,7 +4,8 @@ import { Foo } from './lib';
 const mapping = {
 	'/reroute/basic/a': '/reroute/basic/b',
 	'/reroute/client-only-redirect/a': '/reroute/client-only-redirect/b',
-	'/reroute/preload-data/a': '/reroute/preload-data/b'
+	'/reroute/preload-data/a': '/reroute/preload-data/b',
+	'/reroute/invalidate/a': '/reroute/invalidate'
 };
 
 /** @type {import("@sveltejs/kit").Reroute} */
diff --git a/packages/kit/test/apps/basics/src/routes/reroute/invalidate/+page.server.js b/packages/kit/test/apps/basics/src/routes/reroute/invalidate/+page.server.js
new file mode 100644
index 000000000000..e410319baae7
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/reroute/invalidate/+page.server.js
@@ -0,0 +1,5 @@
+export function load({ isDataRequest }) {
+	return {
+		request: isDataRequest
+	};
+}
diff --git a/packages/kit/test/apps/basics/src/routes/reroute/invalidate/+page.svelte b/packages/kit/test/apps/basics/src/routes/reroute/invalidate/+page.svelte
new file mode 100644
index 000000000000..4f4589198f8d
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/reroute/invalidate/+page.svelte
@@ -0,0 +1,11 @@
+<script>
+	import { invalidateAll } from '$app/navigation';
+
+	export let data;
+</script>
+
+<button on:click={async () => await invalidateAll()}>Invalidate</button>
+
+{#if data.request}
+	<p>data request: {data.request}</p>
+{/if}
diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js
index c8f15f518348..65ead7cb3e1e 100644
--- a/packages/kit/test/apps/basics/test/client.test.js
+++ b/packages/kit/test/apps/basics/test/client.test.js
@@ -1399,6 +1399,12 @@ test.describe('reroute', () => {
 
 		expect(await page.textContent('h1')).toContain('Full Navigation');
 	});
+
+	test('reroute works with invalidate', async ({ page }) => {
+		await page.goto('/reroute/invalidate/a');
+		await page.click('button');
+		await expect(page.locator('p')).toHaveText('data request: true');
+	});
 });
 
 test.describe('init', () => {