SvelteKit dynamic page components
Time for the usual preamble about what Svelte is, what is SvelteKit, what is a component, how is data sent over the internet, what is HTTP, what is caching, what’s a file, where and what is my kitchen sink.
Moving swiftly on.
Svelte’s default behaviour
Its
+page.svelte
’s all the way down!
SvelteKit out of the box will look at your src/routes
directory and traverse each subdirectory in search of specially named JS/TS or Svelte files. The main special files are:
+layout
This is the a wrapper component which will render with subpaths or the page inside the<slot>
element.+page
This comes in 3 flavours:+page.server.js
(yes yes or.ts
) Only rendered on the server+page.js
Rendered on both the server and the client side+page.svelte
The actual Svelte component which will be rendered to the HTML of your page
How other frameworks handle this
The big contender in this field is NextJS. Traditionally Next would look at the src/pages
directory and just render any JS/TS file which default export
’ed a React component.
The newer app router stuff is slightly different though.
Let’s say we want to mimic that same behaviour. ie. we go to http://localhost:5173/some-component
and that renders the component which lives in src/routes/[url]/some-component.svelte
The solution
The file which is called in Svelte kit when a request comes in for a given route is the +page.server
file. Below is an example server file which runs a dynamic import looking for .svelte
files in this directory.
// +page.server.js
import { error } from '@sveltejs/kit';
import { componentUrls } from '../data.js';
/**
* @type {import('./$types.js').PageLoad}
*/
export function load({ params }) {
const url = componentUrls.find(url => url === params.url);
if (!url) throw error(404);
return url;
}
But what is that
componentUrls
array, you ask?
Why, its a glob import old boy!
// data.js
export const componentUrls = Object.keys(
import.meta.glob('./[url]/*.svelte', { eager: true })
).map(path => path.split('[url]/')[1].replace(/\.svelte$/, ''));
By using a glob in the import, we get back a list of files which we can then process into a list of url options for our url to hit.
The next file the request passes through is the +page
file.
This file runs once on the server and once on the client. Here we dynamically import the specific Svelte component and pass the default export as forward in the response object under the key component
(this could be any name by the way).
// +page.js
/**
* @type {import('./$types.js').PageLoad}
*/
export async function load({ params, data }) {
/* @vite-ignore */
const component = await import(`./${params.url}.svelte`);
return {
component: component.default
};
}
Finally we hit the +page.svelte
component. We ingest the data
passed from +page
and pull out the component
key.
<!-- +page.svelte -->
<script>
/**
* @type {import('./$types').PageData}
*/
export let data;
</script>
<svelte:component this={data.component} />
Thankfully Svelte provides us with a handy svelte:component
tag which we can use in our templates to dynamically pass components to be rendered.
And there we have it, we can now go to http://localhost:5173/my-component
and we’ll see the src/routes/[url]/my-component.svelte
file rendered in the browser.