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.
Svelte has pretty quickly taken the top spot for most loved web framework [SO Survey], 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]).
β¨ My personal homepage. A developer portfolio site that aggregates all your projects, blog posts, and stats in one place
β¨ My Website
A re-usable aggregated portfolio and blog site for developers aliciasykes.com
Intro
This is my personal website. It's configurable, so feel free to use it, or any parts of it for yourself :)
About
A self-hosted developer homepage, to showcase your projects, posts, coding stats, and more.
Data is fetched from external sources (GitHub, RSS, social platforms...), so no need for a CMS.
Crafted with SvelteKit + TypeScript- prioritising SEO, performance, accessibility, and compatibility.
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 (LTS or latest) installed. It's also recommended to have Git, a code editor (like VS Code), and access to a terminal. Alternatively, you can use a cloud service, like Codespaces.
Step #1 - Project Setup
We can easily create our project by running:
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).
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:
<script>// All JavaScript logic and imports go here// Append lang="ts" to use TypeScript</script><!-- All markup goes here --><p>Example Component</p><style>//Allstylesgohere,andarescopedtothecurrentcomponent//Appendlang="scss"touseSCSS(oranotherpre-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.
<script lang="ts">// Parametersexportletlevel:'h1'|'h2'|'h3'|'h4'|'h5'|'h6'='h1';// The semantic heading levelexportletcolor:string|undefined=undefined;// An optional override color (defaults to accent)exportletsize:string|undefined=undefined;// An optional override size (default depends on level)exportletfont: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:elementthis={level}style={computedStyles}><slot></slot></svelte:element><style lang="scss">h1,h2,h3,h4,h5,h6{font-weight:700;transition:all.25sease-in-out;font-family:var(--headingFont);color:var(--headingColor);}h1,h2,h3{margin:1rem0;}h4,h5,h6{margin:0.5rem0;}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.
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.
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)
<script>import{page}from'$app/stores';constemojis={// TODO add the rest!404:'π§±',420:'π« ',500:'π₯'};</script><h1>{$page.status} {$page.error.message}</h1><spanstyle="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:
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 to parse the XML response, into JSON.
The following script simply fetches and parses feeds from a given XML RSS feed.
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.
<script lang="ts">/** @type {import('./$types').PageData} */exportletdata;</script>
Blog
{#each data.posts as post}
<li><atarget="_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. 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.
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!
Install the adapter for your desired provider
E.g. for Netlify: npm i --save-dev @sveltejs/adapter-netlify
Import said adaptor in your svelte.config.js file
E.g. import netlifyAdapter from '@sveltejs/adapter-netlify';
Initiate the adaptor, within the config object, under kit
kit: { adapter: netlifyAdapter() }
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:
(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 I've written, with a build stage, deploy stage, and some healthchecks.
It's also published to DockerHub (under lissy93/devolio), so you should be able to use it with docker run -p 3000:80 lissy93/devolio - or use the 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, 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)
Added functionality for loading and combining multiple RSS feeds, as well as sorting and filtering results
Added internationalization functionality (in 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)
Projects Page (fetched from GitHub)
Social Media Links (stats fetched from APIs)
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 (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 :)
If you like this kind of stuff, consider following for more :)