SvelteKit 1.0 - Building a personal blog, that shows your DEV posts 🦄
Tags: svelte, tutorial, opensource, webdev
Published: 2023-02-20 21:58:07

---
title: SvelteKit 1.0 - Building a personal blog, that shows your DEV posts 🦄
published: true
description: Learn SvelteKit and build your own blog, fetching posts from your Dev profile, or any other (RSS-enabled) blogging provider.
tags: #svelte, #tutorial, #opensource, #webdev
cover_image: https://i.ibb.co/jbzfSzs/Screenshot-from-2023-02-20-16-59-37.png
---

> The aim of this post is to provide a whistle-stop tour of the latest version of SvelteKit. We're going to build a developer portfolio and blog website, that fetches data from your RSS feed, as well as the GitHub API.


**Contents**

- [Intro to SvelteKit](#intro-to-sveltekit)
- [What we're going to build](#what-were-going-to-build)
- [Let's get Started!](#lets-get-started)
  * [Step #0 - Prerequisites](#step-0-prerequisites)
  * [Step #1 - Project Setup](#step-1-project-setup)
  * [Step #2 - Finish Setup](#step-2-finish-setup)
  * [Step #3 - Components](#step-3-components)
  * [Step #4 - Creating a Route](#step-4-creating-a-route)
  * [Step #5 - Special Routes](#step-5-special-routes)
  * [Step #6 - Fetching Data](#step-6-fetching-data)
  * [Step #7 - Render Results](#step-7-render-results)
  * [Step #8 - Server-Side](#step-8-serverside)
  * [Step #9 - The Post Route](#step-9-build-the-post-page)
  * [Step #10 - Deploy!](#step-10-deploy)
- [The Final Project](#the-project)
      - [Blog Page (fetched from RSS)](#blog-page-fetched-from-rss)
      - [Projects Page (fetched from GitHub)](#projects-page-fetched-from-github)
      - [Social Media Links (stats fetched from APIs)](#social-media-links-stats-fetched-from-apis)

---

## Intro to SvelteKit

Svelte has pretty quickly taken the top spot for most loved web framework <sup>[[SO Survey]](https://insights.stackoverflow.com/survey/2021#web-frameworks)</sup>, and with the recent release of SvelteKit 1.0, you should expect to see demand for Svelte + SvelteKit developers increase, as more projects adopt it.

SvelteKit to Svelte, is sort of like what Next.js is to React - it handles routing, layouts, server-side rendering, deployment and makes developing quality web apps quicker, easier and much more fun.

But why SvekteKit? ... You'll see! It's just so easy to get a fully-featured dynamic web application up and running, with all the quality metrics which would usually take days, or even weeks to implement in traditional frameworks. Think great performance, simple deployments, easy code structures and a sweet sweet developer experience.

---

## What we're going to build
Most of us have a blog, weather it's here on Dev.to, or on another platform. Today we're going to build and deploy you a personal blog, that aggregates all your posts from other platforms, into a single site.

Since I don't know what blogging platforms you're using, I don't want to rely on individual APIs. But thankfully there's a simple solution to this - RSS! Almost all modern (and old) providers support RSS, and it'll let us easily fetch all your posts from a single URL. (For example, here on DEV: `https://dev.to/feed/[your-username]`).

Here's a live demo: **[devolio.netlify.app/blog](https://devolio.netlify.app/blog)**

And here's the full source: **[@Lissy93/Devolio](https://github.com/Lissy93/devolio)**

{% embed https://github.com/Lissy93/devolio %}

To deploy it yourself - just fork it, update the config with your RSS feed URL(s), and use one of the 1-click deploy options.

---

## Let's get Started!

### Step #0 - Prerequisites
You'll need [Node.js](https://nodejs.org/en/) (LTS or latest) installed. It's also recommended to have [Git](https://git-scm.com/), a code editor (like [VS Code](https://code.visualstudio.com/)), and access to a terminal. Alternatively, you can use a cloud service, like [Codespaces](https://github.com/codespaces/).

---

### Step #1 - Project Setup

We can easily create our project by running:
```shell
npm create svelte@latest dev-blog
```

When prompted, select SvelteKit, then decide weather you'd like TypeScript, ESLint, Prettier, Playwright, Vitest.

Next, we need to navigate into our project (with `cd dev-blog`), and install dependencies (with `npm install`).

To launch the app, with live reload enabled, run:

```shell
npm run dev
```

Then open [`localhost:5173`](http://localhost:5173/)

---

### Step #2 - Finish Setup

To avoid the typical ugly `../../../` in import statements, we're going to add an alias within our `svelte.config.js` file.

This can be done by just adding `alias` object under `config.sveltekit`. Here's an example, where I'll map `./src/` to `$src`.

```javascript
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/kit/vite';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter(),
    alias: {
      '$src/*': 'src/*',
    },
  },
};

export default config;
```

We can come back to the svelte.config file later, as it's where we put adaptors to deploy to various platforms, like Netlify.

If you'd like to use your own Prettier, ESLint or TypeScript config, you can update `.prettierrc`, `.eslintrc.cjsprett` and `tsconfig.json` respectively. Run `npm run format` to apply Prettier rules, and `npm run check` to verify.

---

### Step #3 - Components
Before we proceed, we need to know the basics of components.
One of the reason that Svelte (and SvelteKit) is so easy to work with, is because pretty much everything is just a component. And the structure of components are really, really simple. Here's an example:

```html
<script>
// All JavaScript logic and imports go here
// Append lang="ts" to use TypeScript
</script>

<!-- All markup goes here -->
<p>Example Component</p>

<style>
// All styles go here, and are scoped to the current component
// Append lang="scss" to use SCSS (or another pre-processor)
p {
	color: hotpink;
}
</style>
```

Here's a real-world example, where we're making a re-usable heading component, with optional level (h1, h2, etc), color, size and font.

```html
<script lang="ts">

// Parameters
export let level: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' = 'h1'; // The semantic heading level
export let color: string | undefined = undefined; // An optional override color (defaults to accent)
export let size: string | undefined = undefined; // An optional override size (default depends on level)
export let font: string | undefined = undefined; // An optional override font (defaults to FiraCode)

// Computed values, for reactivity
$: computedColor = color ? `--headingColor: ${color};` : '';
$: computedSize = size ? `--headingSize: ${size};` : '';
$: computedFont = font ? `--headingFont: ${font};` : '';
$: computedStyles = `${computedColor} ${computedSize} ${computedFont}`;

</script>

<svelte:element this={level} style={computedStyles}>
  <slot></slot>
</svelte:element>


<style lang="scss">
  h1, h2, h3, h4, h5, h6 {
    font-weight: 700;
    transition: all .25s ease-in-out;
    font-family: var(--headingFont);
    color: var(--headingColor);
  }

  h1, h2, h3 { margin: 1rem 0; }
  h4, h5, h6 { margin: 0.5rem 0; }

  h1 { font-size: var(--headingSize, 2.8rem); }
  h2 { font-size: var(--headingSize, 2rem); }
  h3 { font-size: var(--headingSize, 1.75rem); }
  h4 { font-size: var(--headingSize, 1.5rem); }
  h5 { font-size: var(--headingSize, 1.25rem); }
  h6 { font-size: var(--headingSize, 1rem); }
</style>
```

Couple of things to note:
- We are defining props with `export let propName`
- We can make props optional, by giving them a default value
- We can access any of these variables within our component, just surround them in braces `{}`
- If we need attributes to be reactive, we use the `$: variabeName` syntax
- We can specify what type of semantic element is used, with `<svelte:element this="div">`
- A method of passing styles from JS into CSS is to define CSS variables, and pass them into the style prop
	- (This isn't as bad as it sounds, as all styles are scoped only to the current component!)


---

### Step #4 - Creating a Route

Next we're going to create a blog page, where all our posts will be displayed. (This could be done on the homepage, in `src/routes/+page.svelte`, but this is a good opportunity to explain routeing)

SvelteKit will automatically create routes based on the directory structure within the `routes` directory. All you need is a directory named after the route name, containing a Svelte file names `+page.svelte`. So let's create that route, with: `touch src/routes/blog/+page.svelte` - the contents of this file will just be a normal Svelte component, like what we saw above.

```html
<script lang="ts">
  let title = 'Blog Page';
</script>

<svelte:head>
  <title>{title}</title> 
</svelte:head>

<h2>{title}</h2>

<style lang="scss">
h2 {
  color: hotpink;
}
</style>
```

We'll also need a route that can render individual posts, but we want that URL path to be dynamic, maybe based on the posts title. For this we can create a directory called `[slug]` that the user will land on when they visit `example.com/blog/example-post`

---

### Step #5 - Special Routes

Now's a good time to mention that we can have our routed inherit certain components that will appear on all pages, like a navbar and footer. For this, we can create a layout file, which needs to be called `+layout.svelte`, and since we want this on all pages, we'll put it into `src/routes`.

Populate this with something like:

```html
<script lang="ts">
  import NavBar from '$src/components/NavBar.svelte';
  import Footer from '$src/components/Footer.svelte';
  import { fade } from 'svelte/transition';
  import { page } from '$app/stores';
</script>

<svelte:head>
  <title>{$page.url.pathname.replaceAll('-', ' ')}</title> 
</svelte:head>

<NavBar />

<main in:fade>
  <slot />
</main>

<Footer />

<style lang="scss">
  @import "$src/styles/color-palette.scss";
  @import "$src/styles/media-queries.scss";
  @import "$src/styles/typography.scss";
  @import "$src/styles/dimensions.scss";

  :global(html) {
    scroll-behavior: smooth;
  }
  :global(::selection) {
    background-color: var(--accent);
    color: var(--background);
  }
</style>
```

A couple of things to note here:
- The main site content will be rendered where `<slot />` is placed
- We're adding a page transition animation, by importing `svelte/transition` and setting `in:fade` on the part of the page which will change
- We can get information about the current page (like path), by using the `page` object (imported from `$app/stores`) - precede it with a `$` to keep the value updated
- If we need to set any tags within the `<head>` we can use `<svelte:head>` to do so
- We can also pop any global style, like a reset or import CSS variables
- Global styles can be applied using `:global(body)` (or whatever selector you're targeting) - but use this sparingly!

Another special route within SvelteKit, is `+error.svelte`, which will be rendered in place of the current route if an error is thrown within the `load()` function of any route.

Again, let's create that file in `src/routes/+error.svelte` and populate it with something like this. (Again, we can get info about the current route, including error code from the `$page` object)

```html
<script>
	import { page } from '$app/stores';

	const emojis = {
		// TODO add the rest!
		404: '🧱',
		420: '🫠',
		500: '💥'
	};
</script>

<h1>{$page.status} {$page.error.message}</h1>
<span style="font-size: 10em">
	{emojis[$page.status] ?? emojis[500]}
</span>
```

It's also worth noting, that you can create layout and error pages that are specific to certain routes, by nesting them within the correct route directory. If you need several layout pages, which share characteristics, you can extract those elements out into their own component, to make them more reusable.

By now, our routes directory structure should look something like this:

```
src/routes
├── +error.svelte
├── +layout.svelte
├── +page.svelte
├── about
│  └── +page.svelte
└── blog
   ├── +page.svelte
   ├── +page.ts
   └── [slug]
      ├── +page.svelte
      └── +page.ts
```

---

### Step #6 - Fetching Data
Now it's time to get into the good stuff! We're going to fetch the list of blog posts, from the users RSS feed.

Now is a good time to mention, that within the path directory for each route, we can also have a `+page.js` / `+page.ts` file (alongside the `+page.svelte`). This is where we'll do our data fetching. 

To keep things simple, we're going to use [`fast-xml-parser`](https://www.npmjs.com/package/fast-xml-parser) to parse the XML response, into JSON.

The following script simply fetches and parses feeds from a given XML RSS feed.

```javascript
import { XMLParser } from 'fast-xml-parser';

const parseXml = (rawRssData) => {
  const parser = new XMLParser();
  return parser.parse(rawRssData);
};

/** @type {import('./$types').PageLoad} */
export const load = () => {
  const RSS_URL = `https://notes.aliciasykes.com/feed`;
  const posts = fetch(RSS_URL)
    .then((response) => response.text())
    .then((rawXml) => parseXml(rawXml).rss.channel.item);
  return { posts };
};
```

---

### Step #7 - Render Results

Rendering the results from the returned data is really easy. In the `blog/+page.svelte` component (next to the `+page.ts` file), simply include `export let data` - this will be the result returned by our fetch function. We can now reference this data in the markup.

```html
<script lang="ts">
  /** @type {import('./$types').PageData} */
  export let data;
</script>

Blog

{#each data.posts as post}
  <li>
    <a target="_blank" href={post.link} rel="noreferrer">
      {post.title}
    </a>
  </li>
{/each}
```

You'll notice we're using `{#each data.posts as post}` - this is just a for loop, as the data returned is an array.

This is part of Svelte's [template syntax](https://svelte.dev/docs#template-syntax). There are other properties also, like `{#if expression}...{/if}` for conditionals, or `{#await expression}...{:then name}...{/await}` for promises, as well as a whole host of other useful features.

---

### Step #8 - Server-Side
What we've got so far works great, but there are a few issues we may encounter with it:
- Load times - RSS feeds are large, and fetching them client-side on each load isn't efficient
- SEO - Dynamically loaded content isn't going to be crawlable by most search engine bots
- CORS - Some RSS feeds won't allow client-side requests from cross-origin hosts

Thankfully, there's an easy fix for this. Renaming `+page.ts` to `+page.server.ts` will cause it to be rendered server-side, instead of on the users browser. This should fix those issues, and won't require any code changes. 

Note that for server-side code, we cannot use any of the browser APIs. Since a lot of our code will be capable of being run both server and client-side, we will need to check certain features are available before attempting to use them. We can do this, by importing `browser` from `$app/environment`, then using `if (browser) { /* Can access browser API here */ }`

---

### Step #9 - Build the Post Page
Finally, when the user clicks on a post, we'd like to render it. This is pretty straitforward, as the RSS response is already in HTML format, so it's just a case of using the `@html` directive, then styling it.

```html
<main class="article-content">
  {@html content}
</main>
```

---

### Step #10 - Deploy!
Now, let's get deploys setup. This is another reason why SvelteKit is so awesome, as deploying to pretty much any provider is just so easy!

1. Install the adapter for your desired provider
	- E.g. for Netlify: `npm i --save-dev @sveltejs/adapter-netlify`
2. Import said adaptor in your `svelte.config.js` file
	- E.g. `import netlifyAdapter from '@sveltejs/adapter-netlify';`
3. Initiate the adaptor, within the config object, under `kit`
	- kit: `{ adapter: netlifyAdapter() }`
4. Deploy! Now just head to your Netlify dashboard, and import the project

If you wish to run your project on a VPS, we can use the `@sveltejs/adapter-node`. Repeat the process above, then run `yarn build`, and start the node server by running `node build/index.js`.

We may want to use multiple adapters, so that our project is compatible with several different hosting providers. Here's an example of my config file which does just this:

```javascript
import autoAdapter from '@sveltejs/adapter-auto';
import netlifyAdapter from '@sveltejs/adapter-netlify';
import vercelAdapter from '@sveltejs/adapter-vercel';
import nodeAdapter from '@sveltejs/adapter-node';

import { vitePreprocess } from '@sveltejs/kit/vite';

const multiAdapter = (adapters) => {
  return {
    async adapt(argument) {
      await Promise.all(adapters.map(item =>
        Promise.resolve(item).then(resolved => resolved.adapt(argument))
      ))
    }
  };
};

/** @type {import('@sveltejs/kit').Config} */
const config = {
  preprocess: vitePreprocess(),
  kit: {
    adapter: multiAdapter([autoAdapter(), netlifyAdapter(), vercelAdapter(), nodeAdapter()]),
    alias: {
      '$src/*': 'src/*',
    },
  },
};

export default config;
```

(don't forget to npm i any adapters before using!)

Finally, let's talk about Docker. As it's a popular deployment method, here's a multi-arch **[`Dockerfile`](https://github.com/Lissy93/devolio/blob/master/Dockerfile)** I've written, with a build stage, deploy stage, and some healthchecks.

It's also published to DockerHub (under [lissy93/devolio](https://hub.docker.com/r/lissy93/devolio)), so you should be able to use it with `docker run -p 3000:80 lissy93/devolio` - or use the [`docker-compose.yml`](https://github.com/Lissy93/devolio/blob/master/docker-compose.yml) as an template for your own container.


---

## The Project

I've had to skip over a few details for the sake of brevity, but all [the code is available on GitHub](https://github.com/Lissy93/devolio), so that should clear up anything that doesn't yet make sense - if it's still not, feel free to ask below :)

There's a few extra features that I also added:
- Extracted all data into a config file, for easy usage, and made it stylable with custom colors and themes
- I used a store to keep track of posts (in [BlogStore.ts](https://github.com/Lissy93/devolio/blob/master/src/store/BlogStore.ts))
- Added functionality for loading and combining multiple RSS feeds, as well as sorting and filtering results
- Added internationalization functionality (in [Language.ts](https://github.com/Lissy93/devolio/blob/master/src/store/Language.ts))
- I built a page to showcase your projects, with data fetched from your GitHub, via their API
- And a contact page, with an email form, social media links and GPG keys
- Added more adapters for deploying to various cloud services, and wrote a Dockerfile

Here's some screenshots (with just the plain theme)

##### Blog Page (fetched from RSS)
![Blog Page](https://i.ibb.co/XVC9YZy/blog-page-gif.gif)

##### Projects Page (fetched from GitHub)
![Projects Page](https://i.ibb.co/nmwLZTr/projects-page.gif)

##### Social Media Links (stats fetched from APIs)
![Social Media Links](https://i.ibb.co/xSvJRbZ/social-previews.gif)

I do plan on expanding the project, add some features and make it into an easily configurable, themeable developer portfolio website, that anyone can easily use. If you'd like to see the updates, drop the repo a star on GitHub :)

And if you'd like to contribute to the source, it's [here](https://github.com/Lissy93/devolio) (`MIT`) on GitHub, and I'll drop you a mention in the credits if you're able to submit a PR!

---

Thanks for sticking by this far! I know this post has been quite long, and is a little different from my usual format. If you've got any feedback, questions, suggestions, or comments - drop them below and I'll reply :)

| <small>If you like this kind of stuff</small>,<br><small>consider following for more :)</small> | [![Follow Lissy93 on GitHub](https://img.shields.io/badge/-Lissy93-3a3a3a?style=flat&logo=GitHub&logoColor=white)](https://github.com/Lissy93)[![Follow Lissy_Sykes on Twitter](https://img.shields.io/badge/-@Lissy_Sykes-00acee?style=flat&logo=Twitter&logoColor=white)](https://twitter.com/Lissy_Sykes) |
|-|-|