Skip to content

Commit 4c5322a

Browse files
authored
Ensure props are loaded from matching endpoint during client-side navigation (#4203)
* add failing test from #4139 * fix #4038 * changeset * add comment
1 parent c8b8d76 commit 4c5322a

File tree

15 files changed

+107
-22
lines changed

15 files changed

+107
-22
lines changed

.changeset/nasty-buttons-invent.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
Ensure props are loaded from matching endpoint during client-side navigation

packages/kit/src/core/dev/plugin.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export async function create_plugin(config, cwd) {
107107
if (route.type === 'page') {
108108
return {
109109
type: 'page',
110+
key: route.key,
110111
pattern: route.pattern,
111112
params: get_params(route.params),
112113
shadow: route.shadow

packages/kit/src/core/generate_manifest/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export function generate_manifest({ build_data, relative_path, routes, format =
6969
if (route.type === 'page') {
7070
return `{
7171
type: 'page',
72+
key: ${s(route.key)},
7273
pattern: ${route.pattern},
7374
params: ${get_params(route.params)},
7475
path: ${route.path ? s(route.path) : null},

packages/kit/src/core/sync/create_manifest_data/index.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -266,20 +266,19 @@ export default function create_manifest_data({
266266

267267
walk(config.kit.files.routes, [], [], [], [layout], [error]);
268268

269-
// merge matching page/endpoint pairs into shadowed pages
269+
const lookup = new Map();
270+
for (const route of routes) {
271+
if (route.type === 'page') {
272+
lookup.set(route.key, route);
273+
}
274+
}
275+
270276
let i = routes.length;
271277
while (i--) {
272278
const route = routes[i];
273-
const prev = routes[i - 1];
274-
275-
if (prev && prev.key === route.key) {
276-
if (prev.type !== 'endpoint' || route.type !== 'page') {
277-
const relative = path.relative(cwd, path.resolve(config.kit.files.routes, prev.key));
278-
throw new Error(`Duplicate route files: ${relative}`);
279-
}
280-
281-
route.shadow = prev.file;
282-
routes.splice(--i, 1);
279+
if (route.type === 'endpoint' && lookup.has(route.key)) {
280+
lookup.get(route.key).shadow = route.file;
281+
routes.splice(i, 1);
283282
}
284283
}
285284

packages/kit/src/core/sync/write_manifest.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export function write_manifest(manifest_data, base, output) {
4848
4949
// optional items
5050
if (params || route.shadow) tuple.push(params || 'null');
51-
if (route.shadow) tuple.push('1');
51+
if (route.shadow) tuple.push(`'${route.key}'`);
5252
5353
return `// ${route.a[route.a.length - 1]}\n\t\t[${tuple.join(', ')}]`;
5454
}

packages/kit/src/runtime/client/client.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ export function create_client({ target, session, base, trailing_slash }) {
567567
if (cached) return cached;
568568
}
569569

570-
const [pattern, a, b, get_params, has_shadow] = route;
570+
const [pattern, a, b, get_params, shadow_key] = route;
571571
const params = get_params
572572
? // the pattern is for the route which we've already matched to this path
573573
get_params(/** @type {RegExpExecArray} */ (pattern.exec(path)))
@@ -618,18 +618,23 @@ export function create_client({ target, session, base, trailing_slash }) {
618618
/** @type {Record<string, any>} */
619619
let props = {};
620620

621-
const is_shadow_page = has_shadow && i === a.length - 1;
621+
const is_shadow_page = shadow_key !== undefined && i === a.length - 1;
622622

623623
if (is_shadow_page) {
624624
const res = await fetch(
625625
`${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json${url.search}`,
626626
{
627627
headers: {
628-
'x-sveltekit-load': 'true'
628+
'x-sveltekit-load': /** @type {string} */ (shadow_key)
629629
}
630630
}
631631
);
632632

633+
if (res.status === 204) {
634+
// fallthrough
635+
return;
636+
}
637+
633638
if (res.ok) {
634639
const redirect = res.headers.get('x-sveltekit-location');
635640

packages/kit/src/runtime/server/index.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,17 @@ export async function respond(request, options, state = {}) {
150150
event.url = new URL(event.url.origin + normalized + event.url.search);
151151
}
152152

153+
// `key` will be set if this request came from a client-side navigation
154+
// to a page with a matching endpoint
155+
const key = request.headers.get('x-sveltekit-load');
156+
153157
for (const route of options.manifest._.routes) {
158+
if (key) {
159+
// client is requesting data for a specific endpoint
160+
if (route.type !== 'page') continue;
161+
if (route.key !== key) continue;
162+
}
163+
154164
const match = route.pattern.exec(decoded);
155165
if (!match) continue;
156166

@@ -163,7 +173,7 @@ export async function respond(request, options, state = {}) {
163173
response = await render_endpoint(event, await route.shadow());
164174

165175
// loading data for a client-side transition is a special case
166-
if (request.headers.get('x-sveltekit-load') === 'true') {
176+
if (key) {
167177
if (response) {
168178
// since redirects are opaque to the browser, we need to repackage
169179
// 3xx responses as 200s with a custom header
@@ -180,9 +190,9 @@ export async function respond(request, options, state = {}) {
180190
}
181191
}
182192
} else {
183-
// TODO ideally, the client wouldn't request this data
184-
// in the first place (at least in production)
185-
response = new Response('{}', {
193+
// fallthrough
194+
response = new Response(undefined, {
195+
status: 204,
186196
headers: {
187197
'content-type': 'application/json'
188198
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/** @type {import('./[a]').RequestHandler} */
2+
export async function get({ params }) {
3+
const param = params.a;
4+
5+
if (param !== 'a') {
6+
return {
7+
fallthrough: true
8+
};
9+
}
10+
11+
return {
12+
status: 200,
13+
body: { param }
14+
};
15+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
/** @type {string} */
3+
export let param;
4+
</script>
5+
6+
<h2>a-{param}</h2>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/** @type {import('./[b]').RequestHandler} */
2+
export async function get({ params }) {
3+
const param = params.b;
4+
5+
if (param !== 'b') {
6+
return {
7+
fallthrough: true
8+
};
9+
}
10+
11+
return {
12+
status: 200,
13+
body: { param }
14+
};
15+
}

0 commit comments

Comments
 (0)