Reputation: 33
Using SvelteKit, I'm trying to load data from the parent route's +layout.server.ts
into the current route's +page.server.ts
load function that the +page.svelte
file gets its data from.
The child route is a full-screen video player which needs the layout to be reset to the root layout since I don't want to render the footer, navbar and other elements on the player route. I do this using the @
method as per the documentation.
However, when using the layout reset, no data is received from the parent load function and the load function of the player route is not even executed. The data prop in the +page.svelte
file is therefore undefined
.
The directory tree for the player path is:
π¦routes
β£ π(app)
β β£ πfilms
β β β£ π[slug]
β β β β£ πplayer
β β β β β£ π+page.server.ts (loads data from the parent at ./../)
β β β β β π+page.svelte (Renders data from the load functions)
β β β β£ π+layout.server.ts (loads data from API)
β β β β π+page.svelte
β β β£ π+page.server.ts
β β β π+page.svelte
β β£ π+layout.svelte
β β π+page.ts
β π+layout.svelte (root layout should be applied to the player route)
The following function loads the data:
// (films/[slug]/+layout.server.ts)
import { error } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types';
export const load = (async ({ fetch, params }) => {
const filmId = params.slug;
const res = await fetch(`/api/films/${filmId}`);
if (!res) throw error(404, 'Film not found');
const filmData = await res.json();
if (!filmData) throw error(404, 'Film not found in database');
return { filmId, filmData };
}) satisfies LayoutServerLoad;
And this function should get the data from the parent load function:
// (films/[slug]/player/+page.server.ts)
import type { PageServerLoad } from './$types';
export const load = (async ({ parent }) => {
const { filmId, filmData } = await parent();
console.log('playerlayout', { filmId, filmData });
return { filmId, filmData };
}) satisfies PageServerLoad;
The code for the +page.svelte file:
<!-- (films/[slug]/player/+page.svelte) -->
<script lang="ts">
import { onMount } from 'svelte';
import { currentFilm } from '$lib/stores/films';
import FilmPlayer from '$lib/components/Player/VimeoPlayer.svelte';
import type { PageData } from './$types';
export let data: PageData;
console.log('player', data);
if (!$currentFilm) {
currentFilm.set(data);
}
const fd = data.filmData;
if (!fd.vimeoId) throw new Error('No video found for film');
const vimeoId = fd.vimeoId;
let urls: {hls:string; lq:string;};
async function videoUrls() {
const response = await fetch('/api/films/videourls', {
method: 'POST',
body: JSON.stringify({ vimeoId }),
headers: {
'content-type': 'application/json'
}
});
urls = await response.json();
}
onMount(() => {
videoUrls();
});
</script>
<main>
<div class="max-h-screen h-full player">
{#if urls}
<FilmPlayer {vimeoId} filmTitle={`${fd.title} (${fd.courseYear-1} - ${fd.courseYear})`} {urls} fullscreen={true}/>
{/if}
</div>
</main>
The project is on the latest versions of its dependencies. That being:
@sveltejs/[email protected]
@sveltejs/[email protected]
@sveltejs/[email protected]
[email protected]
[email protected]
When I don't include the @
character in the filename of the [email protected]
file the loading of data works perfectly, returning the expected result, with the data prop in the player +page.svelte
containing the data loaded from the load function in films/[slug]/+layout.server.ts
. However, by not using the @
method, the player route inherits the layout of the previous route, which is not wanted.
Resetting the layout with a [email protected]
gives the same result. I've also tried different combinations of +page.server.ts
and +page.ts
and +layout.ts
files in both the player and parent route, to load the data, but it gives me the exact same error.
One the first load of the player route upon navigating from the slug, give me a blank page (the root layout), and the following is logged to the console:
playerLoad { filmId: undefined, filmData: undefined }
If I reload the page the following is logged to the console:
playerLoad { filmId: undefined, filmData: undefined }
player { filmId: undefined, filmData: undefined }
TypeError: Cannot read properties of undefined (reading 'vimeoId')
at [email protected]:17:9
at Object.$$render (/node_modules/.pnpm/[email protected]/node_modules/svelte/internal/index.mjs:1974:22)
at Object.default (root.svelte:55:41)
at eval (/src/routes/+layout.svelte:11:81)
at Object.$$render (/node_modules/.pnpm/[email protected]/node_modules/svelte/internal/index.mjs:1974:22)
at root.svelte:43:39
at $$render (/node_modules/.pnpm/[email protected]/node_modules/svelte/internal/index.mjs:1974:22)
at Object.render (/node_modules/.pnpm/[email protected]/node_modules/svelte/internal/index.mjs:1982:26)
at Module.render_response (/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@sveltejs/kit/src/runtime/server/page/render.js:180:29)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
I hope someone can point me in the right direction to fix this issue. I do have some workarounds like adding the player to the parent route, or using stores and coping the load function into the player route so the data can be loaded if the store is not set yet.
Upvotes: 3
Views: 1691
Reputation: 9979
The reason why film data is not loaded when you break out of the layout cascade is because when you break out with /routes/(app)/films/[slug]/player/[email protected]
, you instruct Svelte to only apply the root layout (/routes/+layout.svelte
), meaning that any further layouts in the hierarchy are not applied (/routes/(app)/+layout.svelte
is not applied, which is what you want so that's fine) but also that any further layout load functions are not triggered (the load
function in /routes/(app)/films/[slug]/+layout.server.ts
does not run, which is not what you want).
Breaking out using /routes/(app)/films/[slug]/player/[email protected]
fails for the exact same reason, except it would apply to further descendants as well.
So then, what are your options?
You could add a /routes/(app)/films/[slug]/+layout.svelte
file that clones the root layout and then have your player page apply that layout with /routes/(app)/films/[slug]/player/+page@[slug].svelte
. This would apply the minimal layout (that you cloned) and also insure the load
function from /routes/(app)/films/[slug]/+layout.server.ts
still runs, as it corresponds to the layout hierarchy you are resetting to.
The potential issue with that approach is that now your /routes/(app)/films/[slug]/+page.svelte
is impacted as well, because it will apply this new layout on top of the existing layout hierarchy. If this new layout is a simple <slot/>
then this solution might work.
If the above solution negatively impacts existing pages, then the alternative becomes to get rid of the dependency on /routes/(app)/films/[slug]/+layout.server.ts
to load film data in order to be able to reset your page to the root layout without negative side effects. You can achieve that by simply fetching that film data directly within /routes/(app)/films/[slug]/player/+page.server.ts
:
// (films/[slug]/player/+page.server.ts)
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ fetch, params }) => {
const filmId = params.slug;
const res = await fetch(`/api/films/${filmId}`);
if (!res) throw error(404, 'Film not found');
const filmData = await res.json();
if (!filmData) throw error(404, 'Film not found in database');
return { filmId, filmData };
}) satisfies PageServerLoad;
However, this solution is obviously quite repetititive and not very DRY, so you could instead have a reusable load function say in /lib/load-functions.ts
:
// (/lib/load-functions.ts)
import { error } from '@sveltejs/kit';
export const loadFilms = (async ({ fetch, params }) => {
const filmId = params.slug;
const res = await fetch(`/api/films/${filmId}`);
if (!res) throw error(404, 'Film not found');
const filmData = await res.json();
if (!filmData) throw error(404, 'Film not found in database');
return { filmId, filmData };
});
and then update both /routes/(app)/films/[slug]/+layout.server.ts
and /routes/(app)/films/[slug]/player/+page.server.ts
to make use of that reusable load function:
// (films/[slug]/+layout.server.ts)
import { loadFilms } from '$lib/load-functions';
import type { LayoutServerLoad } from './$types';
export const load = (event) => {
return loadFilms(event);
}) satisfies LayoutServerLoad;
// (films/[slug]/player/+page.server.ts)
import { loadFilms } from '$lib/load-functions';
import type { PageServerLoad } from './$types';
export const load = (event) => {
return loadFilms(event);
}) satisfies PageServerLoad;
Now, because you are no longer relying on /routes/(app)/films/[slug]/+layout.server.ts
to provide film data, you can safely break out from that layout in the hierarchy, so using @
in /routes/(app)/films/[slug]/player/[email protected]
to reset the layout should now work as you originally intended.
Upvotes: 1