Markdown files are directly created in src/lib/content
and are dynamically rendered based on the slug.
How it works
Using vite glob imports are used to automatically import all markdown files
// routes/[slug]/page.ts
import type { ContentModules } from "$lib/types";
import { pathToSlug, slugToPath } from "$lib/utils";
import { error } from "@sveltejs/kit";
import type { EntryGenerator, PageLoad } from "./$types";
export const prerender = "auto";
export const load = (async ({ params }) => {
const modules = import.meta.glob("/src/lib/content/*.md") as ContentModules;
const contentModule = modules[slugToPath(params.slug)];
if (!contentModule) {
error(404, "Can't find content");
}
const { default: component, metadata } = await contentModule().then();
return { component, metadata };
}) satisfies PageLoad;
export const entries: EntryGenerator = async () => {
const modules = import.meta.glob("/src/lib/content/*.md") as ContentModules;
const entries = Object.keys(modules).map((path) => {
return { slug: pathToSlug(path) };
});
return entries;
};
The blog component data imported during the load is rendered as a Svelte component.
<script lang="ts">
// routes/[slug]/page.svelte
import type { PageData } from './$types';
interface Props {
data: PageData;
}
let { data }: Props = $props();
</script>
<h1 class="my-4 text-4xl font-bold">{data.metadata.title}</h1>
<hr class="my-4 border" />
<article
class="prose prose-img:my-0 prose-img:rounded-lg prose-img:mx-auto prose-img:shadow-lg"
>
<data.component></data.component>
</article>