- SvelteKit basic knowledge
- 25 minutes of your time
First, install the necessary packages:
npm install gray-matter marked
npm install @types/node -D
You must also add assetsInclude: ['**/*.md'] to your vite.config.ts, which should look something like this:
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
assetsInclude: ['**/*.md'],
plugins: [sveltekit()]
});Set up the required directory structure as follows:
- In your
libfolder, create anassets/blogdirectory to store your blog posts. - To display these posts, add
+page.svelteand+page.server.tsfiles in theroutesfolder; these will later be used to show all blogs for users to browse. - Next, create a
[slug]directory withinroutes, containing its own+page.svelteand+page.server.tsfiles, which will render individual blog posts. - Finally, add a
blog.tsfile in thelib/serverdirectory—this will act as a helper for loading the blog posts.
Your project structure should look like this:
src
├── lib
│ ├── assets
│ │ └── blog
│ │ ├── blog1.md
│ │ └── blog2.md
│ └── server
│ └── blog.ts
└── routes
├── +page.svelte
├── +page.server.ts
└── [slug]
├── +page.svelte
└── +page.server.ts
Within each Markdown file, you can include metadata that we will utilize later on.
---
title: Creating a Markdown Blog with Sveltekit
date: '2024-11-05'
description: 'How to create a Markdown Blog with Sveltekit'
author: 'John Doe'
tags: ['Tag', 'Tag2']
---
Your content goes hereTo set up our blog, we’ll create some functions inside blog.ts to load and parse Markdown files stored in the /lib/assets/blog directory. We’ll use gray-matter to read both the Markdown content and any metadata (like title, date, and author) from each file. You can learn more about gray-matter here.
The gray-matter library lets us separate metadata, defined in YAML front matter, from the main content of our Markdown files. This metadata could include details like title, date, and tags, which we can extract as follows:
const { data, content } = matter(file);In this example, data holds the parsed metadata, and content contains the Markdown content itself.
Our finished blog.ts file will include three functions:
// src/lib/server/blog.ts
import matter from 'gray-matter';
const modules: any = import.meta.glob('/src/lib/assets/blog/*.md', { eager: true, query: '?raw', import: 'default'
});
function parseMarkdown(file: any) {
const { data, content } = matter(file);
return {
title: data.title,
date: data.date,
description: data.description,
author: data.author,
tags: data.tags,
content,
};
}
export async function LoadBlogPosts() {
const posts = Object.keys(modules).map((filePath) => {
const file = modules[filePath];
const parsed = parseMarkdown(file);
return {
slug: filePath.split('/').pop()?.replace('.md', ''),
...parsed,
};
});
posts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
return { posts };
}
export async function LoadBlogPost(slug: string) {
const filePath = `/src/lib/assets/blog/${slug}.md`;
const file = modules[filePath];
if (!file) {
throw new Error("Post not found");
}
const { title, author, description, date, content } = parseMarkdown(file);
return {
title,
author,
description,
date,
content,
};
}The key points are:
- The
matterlibrary is used to parse the Markdown files and extract the frontmatter data (title, date, etc.) and the content. - The
import.meta.globfunction is used to dynamically import all the Markdown files in the blog directory. - The
LoadBlogPosts()function collects and processes all the blog posts, sorting them by date. - The
LoadBlogPost(slug)function retrieves a single blog post by its slug (the filename without the.mdextension).
To pass data to your pages, we load it in +page.server.ts:
// src/routes/+page.server.ts
import { LoadBlogPosts } from '$lib/server/blog';
export async function load() {
const posts = await LoadBlogPosts();
return { posts };
}In +page.svelte, access the data and display it:
<!-- src/routes/+page.svelte -->
<script lang="ts">
let { data } = $props();
</script>
<div class="posts">
{#each data.posts as post}
<a href="/{post.slug}" class="post">
<h3>{post.title}</h3>
<p>{post.date}</p>
<p>{post.description}</p>
<div class="tags">
{#each post.tags as tag}
<span class="tag">{tag}</span>
{/each}
</div>
</a>
{/each}
</div>This loads the blog posts on the server side and renders them on the page.
Note: The data object contains all the information returned by the LoadBlogPosts function from blog.ts.
Now that we can display our blog posts and have a dynamic path to view individual posts, let’s set up the [slug] directory to view each blog post.
First, load the blog post with your page server using the LoadBlogPost function from blog.ts, as shown here:
// src/routes/[slug]/+page.server.ts
import { LoadBlogPost } from '$lib/server/blog.js';
export async function load({ params }) {
const { slug } = params;
const post = LoadBlogPost(slug)
return post
}In [slug]/+page.svelte, retrieve your data with props and render the content using marked:
<!-- src/routes/[slug]/+page.svelte -->
<script lang="ts">
import { marked } from "marked";
let { data } = $props();
</script>
<a href="/" class="go-back">Go Back</a>
<div class="container">
<div class="content">
{@html marked(data.content)}
</div>
</div>
This setup allows you to dynamically view each blog post by parsing and displaying its content. The data object contains everything returned by LoadBlogPost.
Now, all that's left is to add some styling and error handling, and you’re all set!