Build With Me: Using SvelteKit to Create a Blog With Markdown Posts
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.
- Here’s an excellent article by the creator of Svelte (Rich Harris) on the shortcomings of the Virtual Dom approach
- Both frameworks have excellent official documentation. The interactive tutorial provided at learn.svelte.dev can not be praised enough. They made it incredibly easy to learn and practice by writing bits of code in the browser yourself. With a 2 hour investment, you can learn about 90-95% of Svelte concepts.
- If any snippets from this post confuse you, spend ~ 20 minutes doing the interactive tutorial. I guarantee that will be enough to clear up any issues.
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
- 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
- Navigate to your project directory, run
npm install
to install dependencies - 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
- The
~ % npm create svelte@latest plutonium
~ % cd plutonium
plutonium % npm install
plutonium % npm run dev -- --open
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.
<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>
- If you’re confused by the styling used, check out TailwindCSS and how to install TailwindCSS in your SvelteKit project.
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.
- Create a folder called blog under the routes folder
- Create a +page.svelte file at routes/blog
- Add some content
<div class="...">
<nav class="...">
<a href="/" class="...">Home</a>
<a href="/blog" class="...">Blog</a>
</nav>
<h1>All Plutonium Blog Posts Below</h1>
</div>
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
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:
<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.
<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.
<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 undersrc/lib/
. The reasoning is becauselib
is a special folder in SvelteKit. When you import modules, components, or functions from thesrc/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.
<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.
Engineering the Blog
Now that we understand some of the basic primitive concepts of Svelte, let’s try to accomplish the following.
- Create a component that represents the list of blog posts available to read. Each element is a link to the blog post.
- 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.
- Enables use of
- 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.
- Why is Pluto No Longer Classified as a Planet?
- Should Pluto Still Be Considered a Planet?
I’ve written a BlogPosts.svelte
component that links out to these individual pages.
<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
<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.
Markdown in Svelte using mdsvex
Installation
mdsvex is an npm package, use your command line to install.
npm i -D mdsvex
Next, we’ll need to add mdsvex to our config. Open svelte.config.js
, and modify the code like so:
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.
- Why is Pluto No Longer Classified as a Planet?
- Should Pluto Still Be Considered a Planet?
Create two folders under routes/blog/
. Then create two +page.md
files, one for each blog post.
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.
# Why is Pluto No Longer Classified as a Planet?
Because haters going to hate
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>)
- Create
(home)
&(blog)
folders at/routes
folder - Move the
/blog
folder into the(blog)
folder. - 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)
.
- Also move the
- Create a new
+layout.svelte
at(blog)/blog/
Here’s what the updated routes/
folder structure would look like after these steps.
Here’s what I’ve added to the new svelte layout
<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.
- 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>
- Under the hood
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.
My Recommended Approach to CSS for Markdown
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:
npm install -D @tailwindcss/typography
Then add the plugin to your tailwind.config.js
file:
/** @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.
// ...
<article class="prose w-[720px] mx-auto">
<slot />
</article>
// ...
The result
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
// ...
<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.
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