First 100 customers in 2024 get an extra 50% off. Don't miss out on this opportunity!

November 11, 2024

Create Blog with SvelteKit

Create Blog with SvelteKit

If youโ€™ve ever wanted to create a blog with SvelteKit, youโ€™ve come to the right place. Thereโ€™s two pieces you need: mdsvex and a simple function to generate a list of blog posts.

Setup mdsvex

To install mdsvex, run the following command:

bun i -D mdsvex

Then add it to svelte.config.js file:

import { mdsvex } from "mdsvex";

/** @type {import('@sveltejs/kit').Config} */
const config = {
  preprocess: [
    mdsvex({
      extensions: [".md"],
      layout: {
        _: "./src/routes/blog/Layout.svelte",
      },
    }),
  ],
  extensions: [".svelte", ".md"],
};

If you have a specific layout you want to use for each blog post, create a Layout.svelte file in the src/routes/blog directory. Then you can get the frontmatter as props in your layout like this:

<script lang="ts">
  import { type Snippet } from "svelte";

  // Props
  const {
    title,
    description,
    children,
  }: {
    title: string;
    description: string;
    children: Snippet;
  } = $props();
</script>

<svelte:head>
  <title>{title} - SvelteRust</title>
  <meta name="description" content="{description}" />
</svelte:head>

<article>
  <h1>{title}</h1>
  {@render children()}
</article>

So when you have a create a blog post like this, the frontmatter will be available as props in Layout.svelte:

---
title: Create Blog with SvelteKit
description: Learn the easiest way to create a blog with SvelteKit.
tags: sveltekit, blog
---

If you've ever wanted to create a blog with SvelteKit, you've come to the right place.
There's two pieces you need: `mdsvex` and a simple function to generate a list of blog posts.

Generate a list of blog posts automatically

To generate a list of blog posts automatically, you can use following +page.server.ts file which you put in the src/routes/blog directory:

import fs from "fs";
import type { PageServerLoad } from "./$types";

type Markdown = {
  metadata: {
    title: string;
    description: string;
    tags: string;
  };
};

export const load: PageServerLoad = async () => {
  // Get all articles with metadata
  const articles = Object.entries(import.meta.glob<Markdown>("/src/routes/blog/**/*.md", { eager: true })).map(([path, { metadata }]) => {
    const stats = fs.statSync(`.${path}`);
    const url = path.replace("/src/routes/blog/", "").replace("/+page.md", "");
    const tags = metadata.tags.split(",").map((tag) => tag.trim());
    return { ...metadata, url, tags, created: stats.birthtime };
  });
  articles.sort((a, b) => b.created.getTime() - a.created.getTime());
  return { articles };
};

Then you can render each article nicely in +page.svelte, same directory as the above +page.server.ts file:

<script lang="ts">
  import { Badge } from "$ui/badge";

  // Props
  const { data } = $props();
  const { articles } = $derived(data);
</script>

<svelte:head>
  <title>Blog - SvelteRust</title>
  <meta
    name="description"
    content="Explore the SvelteRust blog for tutorials and tips on SvelteKit and Rust. Stay updated with the latest in programming insights."
  />
</svelte:head>

<h1 class="h2">Blog</h1>
<p class="p mt-4">
  Check out the latest articles from SvelteRust below. The articles are mostly tutorials made for SvelteKit and Rust, but also contain tips
  and tricks.
</p>

<div class="mt-6 grid gap-4">
  {#each articles as article}
  <a href="/blog/{article.url}" class="hover:bg-muted rounded border p-4 duration-100">
    <p class="p !text-slate-500">
      {new Date(article.created).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" })}
    </p>
    <h2 class="h3">{article.title}</h2>
    <p class="p mt-2">{article.description}</p>

    {#if article.tags && article.tags.length > 0}
    <div class="mt-2 flex gap-2">
      {#each article.tags as tag}
      <Badge class="text-md">{tag}</Badge>
      {/each}
    </div>
    {/if}
  </a>
  {/each}
</div>

Your blog directory should look like this. Just create a new folder with the slug/url you want, and create a +page.md file inside it:

blog
โ”œโ”€โ”€ create-blog-with-sveltekit
โ”‚ย ย  โ””โ”€โ”€ +page.md
โ”œโ”€โ”€ Layout.svelte
โ”œโ”€โ”€ +page.server.ts
โ””โ”€โ”€ +page.svelte

And thatโ€™s all you need to create a simple blog with SvelteKit that is automatically generated from your markdown files.

BONUS: Generate RSS Feed

To generate a RSS feed, move the logic for getting articles into src/lib/articles.ts, then add the following to src/routes/rss/+server.ts:

import { getArticles } from "$lib/articles";

export async function GET() {
  const articles = getArticles();
  const xml = `
    <?xml version="1.0" encoding="UTF-8" ?>
    <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
      <channel>
        <title>SvelteRust</title>
        <link>https://svelterust.com</link>
        <description>Articles about tutorials made for SvelteKit and Rust, as well as tips and tricks.</description>
        <atom:link href="https://svelterust.com/rss" rel="self" type="application/rss+xml" />
        ${articles
          .map(
            (article) => `
          <item>
            <title>${article.title}</title>
            <link>https://svelterust.com/blog/${article.url}</link>
            <description>${article.description}</description>
            <pubDate>${article.created.toUTCString()}</pubDate>
            <guid>https://svelterust.com/blog/${article.url}</guid>
          </item>
        `,
          )
          .join("")}
      </channel>
    </rss>
  `.trim();

  return new Response(xml, {
    headers: {
      "Content-Type": "application/xml",
    },
  });
}