← All posts

How this site is built, part 2: adding a blog with MDSvex

In part 1 I covered the basic SvelteKit + GitHub Pages setup. This time: how to add a Markdown-powered blog using MDSvex.

MDSvex is a preprocessor that compiles .md (and .svelte.md) files into Svelte components. That means you can use Svelte components inside your Markdown — handy for interactive demos, callout boxes, whatever you need.

Install MDSvex

npm i -D mdsvex

Configure it

In svelte.config.js, import and apply the preprocessor. You also need to tell SvelteKit that .md files are valid Svelte modules:

import adapter from '@sveltejs/adapter-static';
import { defineMDSveXConfig, mdsvex } from 'mdsvex';
import { vitePreprocess } from '@sveltejs/kit/vite';

const mdconfig = defineMDSveXConfig({
  extensions: ['.svelte.md', '.md', '.svx'],
  smartypants: true  // curly quotes, em-dashes, etc.
});

const config = {
  extensions: ['.svelte', ...mdconfig.extensions],
  preprocess: [vitePreprocess(), mdsvex(mdconfig)],
  kit: {
    adapter: adapter()
  }
};

export default config;

smartypants: true is a small but nice addition — it converts straight quotes to curly quotes and -- to em-dashes automatically.

Write posts as Markdown

Drop .md files in src/posts/. Each file gets a frontmatter block:

---
title: 'My post title'
description: 'A short summary for the index page'
date: '2023-05-01'
author: 'Your name'
published: true
tags: ['sveltekit', 'webdev']
---

Your content here.

published: false acts as a draft flag — unpublished posts are filtered out before they’re shown.

Load posts dynamically

SvelteKit’s import.meta.glob can discover all post files at build time:

// src/routes/+page.server.ts
const modules = import.meta.glob('/src/posts/*.{md,svx,svelte.md}');

Each resolved module exposes default (the compiled Svelte component) and metadata (the frontmatter). From there, you filter by published, sort by date, and pass posts to the page.

Render a single post

For the posts/[slug] route, the same glob is used to find the matching file:

// src/routes/posts/[slug]/+page.ts
for (const [path, resolver] of Object.entries(modules)) {
  if (slugFromPath(path) === params.slug) {
    match = { path, resolver };
    break;
  }
}

The slugFromPath helper strips the path prefix and file extension to give you a clean slug:

export const slugFromPath = (path: string) =>
  path.match(/([w-]+).(svelte.md|md|svx)/i)?.[1] ?? null;

Once you have the resolved module, render it with <svelte:component this={post.default} />.

Syntax highlighting

MDSvex bundles Prism for code blocks. To apply a theme, import a Prism CSS file in your layout:

<!-- src/routes/+layout.svelte -->
<script>
  import './prism-vsc-dark-plus.css';
</script>

You can grab any theme from prism-themes or write your own.

That’s the whole setup. The result is a zero-server, statically-generated blog where posts are just Markdown files in a folder — no CMS, no database.