Build With Me: Using SvelteKit to Create a Blog With Markdown Posts

Build With Me: Using SvelteKit to Create a Blog With Markdown Posts


The Author of this piece

Liam Fernandez

Published: Sep 13th, 2023


Introduction

Welcome to the liamverse. My intentions for this blog isn’t only about technicalities; I desire to embrace writing, immortalize my learnings, and share them with the world.

Writing is the single most important skill that will help level up your career.

My Father

It feels right that the inaugural post is a breakdown of how this blog is built. Join me as I unravel the secrets of how to build your very own blog, using the remarkable power of Svelte and SvelteKit. If you’re familiar with the principles of writing declaritive UI and/or have used React before, you will grasp onto Svelte very quickly. Let’s jump into it.

You’ll get the most out of this tutorial if you have basic understanding of HTML, CSS, and JavaScript.

What is Svelte/Kit?

To understand SvelteKit, one must first start with Svelte.

Svelte is a modern JavaScript framework for building user interfaces. Unlike frameworks like React or Vue, which use a virtual DOM to update the UI, Svelte compiles components into highly optimized vanilla JavaScript at build time. This approach eliminates the need for a virtual DOM, resulting in faster initial load times and smoother interactions. Svelte allows developers to write less boilerplate code by incorporating reactivity directly into the language. With its single-file component architecture, you can define a component’s behavior, look, and markup all in one place, making it incredibly intuitive and efficient for developing web apps.

SvelteKit is an extension of Svelte that provides a full-featured framework for building entire web applications. It comes with built-in solutions for routing, server-side rendering (SSR), and static site generation (SSG), offering a more comprehensive toolset right out of the box. SvelteKit aims to simplify the development process by taking care of common tasks and configurations, allowing you to focus on writing the core logic of your application. It leverages ‘adapters’ to deploy your project easily on various hosting platforms without worrying about server configurations. Essentially, while Svelte is perfect for building highly interactive and efficient UI components, SvelteKit provides the toolkit to build and deploy full-fledged web applications.

Svelte vs React

Svelte and React are both tools that accomplish the same goal. They are javascript frameworks that enable you to build your UI into declarative, state-driven components.

They differ because Svelte is a compiler and not a runtime library. Svelte sets itself apart from virtual DOM-based frameworks like React and Vue by eliminating the need for a virtual DOM altogether. In virtual DOM architectures, the framework creates a lightweight in-memory representation of the actual DOM. When changes occur, the virtual DOM gets updated first, followed by a “diffing” algorithm that compares the new virtual DOM with the old one to find the minimal number of changes required to update the real DOM. This process, while effective, involves computational overhead.

Svelte, on the other hand, compiles components into optimized JavaScript code during build time. This code updates the real DOM directly, sidestepping the diffing process and reducing runtime costs. The result is a faster, more efficient way to update the user interface, offering both quicker initial load times and more fluid interactions.

Writing Code in Svelte vs JSX (React)

Svelte and JSX serve similar purposes but go about it in distinct ways. JSX, used predominantly with React, is essentially a syntax extension for JavaScript that allows you to write HTML-like elements directly within your JS code. While JSX brings the markup into the logic, it’s not a templating language, and you still need to handle component state and behavior using JavaScript.

Svelte, on the other hand, provides a more integrated approach by offering a single-file component architecture that combines markup, styling, and logic in a cohesive manner. Unlike JSX, which requires transpilation to turn JSX elements into JavaScript function calls, Svelte components are compiled into plain JavaScript, automating away many of the updates and boilerplate you would have to manually specify in JSX. This makes Svelte’s syntax arguably cleaner and more intuitive, especially for those coming from an HTML/CSS background, though it may take some adjustment if you’re deeply rooted in the JSX way of doing things.

I’ve worked with React for many years and I have to say, in my opinion, Svelte has a much better developer experience.

Me

Start Coding

The simplest way to scaffle a new project with SvelteKit is through your command line, using npm create svelte@latest

  • For the sake of this build with me, I’m going to call this project Plutonium and the hypothetical end goal is to host this blog at plutonium.com svelte-create
  • Select Skeleton project as we are building our blog as a website and not a library project.

How to Develop Locally

Now that we’ve created a skeleton SvelteKit project

  1. Navigate to your project directory, run npm install to install dependencies
  2. In order to start a dev server, run npm run dev -- --open
    • The --open flag will automatically pop open localhost:5173 on your preferred browser
Terminal
~ % npm create svelte@latest plutonium
~ % cd plutonium
plutonium % npm install
plutonium % npm run dev -- --open

skeleton-page

The page we can see at localhost:5173 is the entry point of our blog and contains some starter text. In order to jazz up the homepage, let’s first cover how SvelteKit scaffolds the project so we understand exactly what we’re changing and why.

SvelteKit’s Routing System

In web development, a routing system for a specific website is like a traffic director that helps visitors find different pages on that website. It’s like a map that guides them to the right places when they click on links or type in specific addresses.

In a website with a file-based routing system, each page has its own separate file. When someone visits a page, the routing system simply looks for the corresponding file to show. It’s like having different rooms in a house, and you go to each room by opening different doors.

On the other hand, for a single-page app (SPA), all the content is loaded in one main file. The routing system knows which parts of that file to show when someone wants to see a different page. It’s like having a magic room that can change its appearance to become whatever you need it to be.


SvelteKit makes use of a file based routing system. Every SvelteKit project contains a /routes folder. When you want to add a new page to our site, you would add a subdirectory under /routes/<name of new page> and then add a +page.svelte in that subdirectory.

  • The entry point to the website will always be /routes/+page.svelte

SvelteKit has rigid naming conventions, if the file is named anything other than +page.svelte, it will not be recognized when you navigate to that url resulting in a 404 error.

Modifying the Home Page

Open the project directory in your editor of choice. We learned earlier, that the entry point is written at routes/+page.svelte. I’m going to add a Navigation Bar with two buttons, one for ‘home’ and one for ‘blog’. Then some text under it.

src/routes/+page.svelte
<div
  class="pt-10 bg-[#1c1c1c] text-white
  flex flex-col gap-4 items-center min-h-screen"
>
  <nav class="flex flex-row gap-12 my-8 text-3xl items-center">
    <a href="/" class="p-4 rounded-xl bg-purple-800">Home</a>
    <a href="/blog" class="p-4 rounded-xl bg-purple-800">Blog</a>
  </nav>
  <h1>This is the <strong>home</strong> page.</h1>
</div>

home-page Nice, now we have navigation buttons to take us back to this page (home) and to take us to plutonium.com/blog. However, when we press the blog button, we get a 404 error because that page doesn’t exist yet in our project. Let’s fix that.

Adding a New Page to Our Site

The /blog page is where I want to have a list of all posts that can be read. Each element of the list should be clickable and link to the blog post itself written in markdown.

  1. Create a folder called blog under the routes folder
  2. Create a +page.svelte file at routes/blog
  3. Add some content
src/routes/blog/+page.svelte
<div class="...">
  <nav class="...">
    <a href="/" class="...">Home</a>
    <a href="/blog" class="...">Blog</a>
  </nav>
  <h1>All Plutonium Blog Posts Below</h1>
</div>

blog-page Notice: We have some duplicated code. The navigation buttons are defined in both +page.svelte files for the home page and the blog page. This violates the DRY (Don’t Repeat Yourself) design pattern. Not to worry, we can abstract out any UI components that should be present in multiple pages using layouts.

Layouts in SvelteKit

Different routes of your app will often share common UI. Instead of repeating it in each +page.svelte component, we can use a +layout.svelte component that applies to all routes in the same directory.

In this app we have two routes

  • src/routes/+page.svelte
  • src/routes/blog/+page.svelte

They contain the same navigation UI. Let’s create a new file

  • src/routes/+layout.svelte
Current Project Structure
src/routes/
├ blog/
│ └ +page.svelte
├ +layout.svelte
└ +page.svelte

…and move the duplicated content from the +page.svelte files into the new +layout.svelte file. The <slot /> element is where the page content will be rendered:

src/routes/+layout.svelte
<div class="...">
  <nav class="...">
    <a href="/" class="...">Home</a>
    <a href="/blog" class="...">Blog</a>
  </nav>
  <slot />
</div>

Couldn’t explain it better than the team themselves, this section was adapted from Svelte Official Docs: Layouts

Me

Components in Svelte

One of the most primitive features in Svelte is being able to break your UI into ”components”. Logical chunks of UI can be separated into their own file, and then imported wherever needed.

For example, let’s make a NavBar component, this will hold the 2 navigation buttons we’ve already created previously.

src/lib/components/NavBar.svelte
<nav class="flex flex-row gap-12 my-8 text-3xl items-center">
  <a href="/" class="p-4 rounded-xl bg-purple-800">Home</a>
  <a href="/blog" class="p-4 rounded-xl bg-purple-800">Blog</a>
</nav>

Let’s also create a footer component and import both newly created components into our +layout.svelte file.

src/lib/components/Footer.svelte
<footer
  class="px-40 w-full bg-purple-800 text-white
  flex flex-row justify-center"
>
  <p>For more blog posts, check out liamverse.io</p>
</footer>
  • Notice: I’ve made these files in a components directory under src/lib/. The reasoning is because lib is a special folder in SvelteKit. When you import modules, components, or functions from the src/lib directory, you can use the $lib alias instead of specifying a relative path. This makes your code cleaner and more concise. This is how SvelteKit is configured out of the box.
src/routes/+layout.svelte
<script>
  import Footer from "$lib/components/Footer.svelte";
  import NavBar from "$lib/components/NavBar.svelte";

  import "../app.css";
</script>

<div
  class="pt-10 bg-[#1c1c1c] text-white
  flex flex-col justify-between gap-4 items-center min-h-screen"
>
  <NavBar />
  <slot />
  <Footer />
</div>

Now our layout file is much cleaner and we see how to abstract out common pieces of UI into their own file. Here’s what our site looks like this far.

current progress

Engineering the Blog

Now that we understand some of the basic primitive concepts of Svelte, let’s try to accomplish the following.

  1. Create a component that represents the list of blog posts available to read. Each element is a link to the blog post.
  2. Set up MDSVEX so that markdown files will go through same compilation step as .svelte files.
    • Enables use of +page.md to treat a .md file as it’s own route on the site.
    • The compiler will transform markdown into HTML and you can even write Svelte code in your markdown files.
mdsvexgif
https://mdsvex.com
  1. Style our blog post with another +layout.svelte component.

Create a Component for List of Blog Posts to Read

Our /blog page currently has nothing but text. Let’s change it so that there is a list of links that can be clicked to take you to the page for each respective blog post.

Since our blog is called plutonium, the first two posts blog posts will be titled the following.

  1. Why is Pluto No Longer Classified as a Planet?
  2. Should Pluto Still Be Considered a Planet?

I’ve written a BlogPosts.svelte component that links out to these individual pages.

src/lib/components/BlogPosts.svelte
<script>
  const posts = [
    {
      title: 'Why is Pluto No Longer Classified as a Planet?',
      slug: 'why-is-pluto-no-longer-a-planet'
    },
    {
      title: 'Should Pluto Still Be Considered a Planet?',
      slug: 'should-pluto-still-be-a-planet'
    }
  ];
</script>

<ul class="flex flex-col gap-12 text-blue-300">
  {#each posts as post}
    <li>
      <a
        class="outline outline-1 outline-white p-2 rounded-xl hover:text-red-300 hover:bg-[#b8b8b856]"
        href={'/blog/' + post.slug}
      >
        {post.title}
      </a>
    </li>
{/each}
</ul>

Change our routes/blog/+page.svelte accordingly

routes/blog/+page.svelte
<script>
	import BlogPosts from '$lib/components/BlogPosts.svelte';
</script>

<h1>All Plutonium Blog Posts Below</h1>
<BlogPosts />

Now our blog page links out to our blog posts. blog posts component

Markdown in Svelte using mdsvex

Installation

mdsvex is an npm package, use your command line to install.

Terminal
npm i -D mdsvex

Next, we’ll need to add mdsvex to our config. Open svelte.config.js, and modify the code like so:

svelte.config.js
import { vitePreprocess } from '@sveltejs/kit/vite';
import { mdsvex } from 'mdsvex';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	kit: { ... },

	extensions: ['.svelte', '.md'],

	preprocess: [vitePreprocess(), mdsvex({
		extensions: ['md']
	})],
};

export default config;

Notice: After modifying the config, be sure to restart the dev server

Pages in Markdown

When it comes to integrating Markdown files within your SvelteKit application, there are two broad strategies to consider.

Scalable Approach: Single Folder for All Posts

This method entails storing all your markdown posts in a single location. This could be situated in the lib or can be closely tied to your template page. The slug for each blog post will derive its name directly from its respective markdown file.

By leveraging a +page.server.ts file, you can create a server function that serves the processed HTML content of the markdown file to the designated page.

  • Cons: Can’t host with popular providers such as vercel because they host server-only code via edge deployment or serverless functions. This means code written there won’t have access to the file system.
  • Pros: If you want to run a version of this blog we are building here at scale, you would likely move the writing and storing of your markdown files away from the codebase entirely and implement a database or CMS (content management system). If you were to make that change, you would need to implement a +page.server.ts call that retrieves content from that database anyway.

Our Approach: Dedicated Folder for Each Post

To keep this tutorial simple, we’re going with the straightforward approach of creating a unique route for every blog post you write. The folder’s name for each post acts as the slug for that particular post.

  • We will ensure that every markdown file within these folders is named +page.md. This naming convention is crucial for the markdown file to be recognized as a distinct page.
  • This will work at any scale, just have to be comfortable with re-deploying your website each time you add a new markdown file (post) to the codebase. Also if you write a lot of content, the amount of folders one created could build up a level of clutter that could sour the developer experience.

As you read through these options, choose the one that best aligns with your project’s structure and your personal workflow preferences. Both methods have their merits; it’s all about what makes sense for your setup and eventual deployment.

Creating Routes for Each Blog Post

As mentioned before, since our blog is called plutonium, the first two posts blog posts will be titled the following.

  1. Why is Pluto No Longer Classified as a Planet?
  2. Should Pluto Still Be Considered a Planet?

Create two folders under routes/blog/. Then create two +page.md files, one for each blog post.

Current blog structure
src/routes/blog/
├ why-is-pluto-no-longer-a-planet/
│ └ +page.md
├ should-pluto-still-be-a-planet/
│ └ +page.md
└ +page.svelte // page that links out to individual blog posts

After I’ve added some content to the markdown files, let’s see the page for the Why is Pluto No Longer Classified as a Planet? post.

blog/why-is-pluto-no-longer-a-planet/+page.md
# Why is Pluto No Longer Classified as a Planet?
Because haters going to hate

current progress

Styling Blog Posts

From the last image you can see the text I’ve written is present, but it’s sandwiched in between the header and footer defined in our +layout.svelte written earlier.

QUESTION: What if I don’t want this layout to apply to the blog posts?

ANSWER: We need to modify the routes folder structure into separate folders depending on how many root layouts we want to exist within our blog. SvelteKit dictates that these folders must be in parentheses e.g (<folder name>)

  1. Create (home) & (blog) folders at /routes folder
  2. Move the /blog folder into the (blog) folder.
  3. Move the existing routes/+page.svelte & routes/+layout.svelte into the (home) folder.
    • Also move the routes/blog/+page.svelte file to (home)/blog because we want the existing layout to apply to this page. Yes that’s right, create another /blog folder. This means there are two /blog. One under (home) and another under (blog).
  4. Create a new +layout.svelte at (blog)/blog/

Here’s what the updated routes/ folder structure would look like after these steps. Updated folder structure

Here’s what I’ve added to the new svelte layout

(blog)/blog/+layout.svelte
<script>
  import { goto } from '$app/navigation';
</script>
<div class="pt-10 bg-[#1c1c1c] text-white min-h-screen">
  <span class="flex justify-center gap pb-20">
    <button
      on:click={() => {
        goto('/blog');
      }}
      class="bg-purple-800 text-white font-bold py-2 px-4 rounded">
        Back to all posts
    </button>
  </span>
  <slot />
</div>
  • Note: The <slot /> element is where the markdown content will be living on the page.

Now let’s check the page for Why is Pluto No Longer Classified as a Planet? again. updated blog post

  • We can tell the new layout is being used because there is only a single button that takes us back to the /blog page.
  • However, why is the content from the markdown file just showing as unstyled text? It’s left aligned and the header line is the same size as the text under it.
    • Under the hood mdsvex is doing it’s job. It is translating the markdown into html. The first line is a <h1> element and the second line is a <p>

Inspected elements

Those elements aren’t being styled! We have a couple of options to address this. There is a slight caveat, this html is being rendered from markdown so you can’t apply styles traditionally.

  • The traditional approach would be to create a new stylesheet (.css file) and import that at the top of the +layout.svelte file. However, if you do this, styles will bleed through to other pages unexpectedly.

I recommend using the Official Tailwind Typography Plugin.

The official Tailwind CSS Typography plugin provides a set of prose classes you can use to add beautiful typographic defaults to any vanilla HTML you don’t control, like HTML rendered from Markdown, or pulled from a CMS.

Tailwind Docs

We can essentially wrap our markdown content in an element (<div>, <article>, etc...) and apply a single prose class provided by this plugin. This gives us acceptable default styling for every html element. These styles also never bleed to other pages. The plugin also allows for optionality, you can customize any element’s stylings by adding config options for the plugin in the tailwind.config.js.

Installation

Install the plugin from npm:

Terminal
npm install -D @tailwindcss/typography

Then add the plugin to your tailwind.config.js file:

tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
  content: ['./src/**/*.{html,js,svelte,ts}'],
  theme: {
    // ...
  },
  plugins: [
    require('@tailwindcss/typography'),
    // ...
  ],
}

Now we have the prose class available to us. Let’s add it to the +layout.svelte that wraps our blog posts.

routes/(blog)/blog/+layout.svelte
// ...
<article class="prose w-[720px] mx-auto">
  <slot />
</article>
// ...

The result Prose class applied to blog post

The spacing and sizing looks good, BUT we can barely read the text. These colors aren’t going to cut it. Here’s where we are going to explore how to customize the styling to your liking.

The @tailwindcss/typography plugin provides unique modifiers for each element, so to change all <h1> elements and <p> elements to have a different text color, modify the code like below

routes/(blog)/blog/+layout.svelte
// ...
<article class="prose prose-h1:text-white prose-p:text-white w-[720px] mx-auto">
  <slot />
</article>
// ...

Usage: I used the prose-h1: modifier to apply a regular tailwind class (text-white) so that text is white only for <h1> html elements.

  • @tailwindcss/typography plugin provides one of these modifiers for every relevant html element that is produced by a Markdown file. This allows for full customization of how you want your blog posts to be styled.

Correct colors on Blog Post text

Voilá, we’ve accomplished the three goals we set out for at the beginning of the Engineering the Blog section. At it’s very core, this is how I’ve set up this blog.

I’ve set up a repo with the starter code developed for this plutonium blog. You can check out the code on GitHub