Hosting my SvelteKit site and blog on SourceHut
First, some background: This year, I redid my personal site, the one you’re on now. For my previous site that I made back in 2021, I built it using Sapper, the predecessor to SvelteKit. I hosted my code on GitLab, which would automatically deploy to Netlify.
For this iteration of my site, I decided to go just go full in with SourceHut—both for hosting my git repository and for hosting the resultant static site! Just like GitHub Pages or GitLab Pages, there is also, reasonably enough, SourceHut Pages. I’ve been interested in SourceHut because of its strong alignment with open-source; this writeup at parasrah.com is a helpful rundown on SourceHut’s positives.
In my web searches, I didn’t find much info on how to specifically deploy SvelteKit with SourceHut pages, so I pieced together things based on resources for deploying other static site generator code on SourceHut. I thought I’d write it down in case it’s useful to someone else~
Building SvelteKit as a static site
Because SourceHut pages can only deploy static sites, we have to build our SvelteKit project as a static files using adapter-static.
For example, my svelte.config.js looks like this:
import adapter from '@sveltejs/adapter-static';
import { mdsvex } from 'mdsvex';
import mdsvexConfig from './mdsvex.config.js';
/** @type {import('@sveltejs/kit').Config} */
const config = {
extensions: ['.svelte', ...mdsvexConfig.extensions],
preprocess: [mdsvex(mdsvexConfig)],
kit: {
adapter: adapter({
fallback: '404.html'
})
}
};
export default config; SourceHut configuration
To automatically deploy on SourceHut, we need a .build.yml.
I based mine off of this Eleventy sample, and ended up with this:
image: alpine/edge
oauth: pages.sr.ht/PAGES:RW
packages:
- nodejs
- npm
- hut
environment:
repo: <REPO_NAME>
site: <SITE_URL>
# Disable npm progress bar
# https://docs.npmjs.com/cli/v9/using-npm/config#progress
npm_config_progress: 'false'
secrets:
- <UUID_FOR_SECRET>
tasks:
- install: |
cd $repo
npm install
- package: |
cd $repo
npm run build
tar -C build -cvz . > ../site.tar.gz
- upload: |
hut pages publish -d $site site.tar.gz Blog setup
I want to get into blogging for the sake of documenting technical things like this and also just to practice writing more. :-)
I knew I wanted to be able to write posts as separate Markdown files. To that end, I found this blog post by Josh Collinsworth incredibly helpful. I don’t really have anything else to add, but if you’re interested in doing similar, I’d start there! Most of my setup is identical.
I also took some inspiration for my MDsveX settings from this blog post by Jeff Pohlmeyer. So my mdsvex.config.js looks like this:
import { defineMDSveXConfig as defineConfig } from 'mdsvex';
const config = defineConfig({
extensions: ['.md', '.svx'],
smartypants: { quotes: true, ellipses: true, dashes: 'oldschool' }
});
export default config; In particular, I love the smartypants options, which save me time from figuring out how to make curly quotes in my code editor ha.
RSS feed
For my RSS feed, I wanted to be able to have the full contents of each post show up in RSS feed readers, so I referenced Kyle Nazario’s posts on the topic (part one + part two), which also build on Josh Collinsworth’s guide. At the moment, mine is a bit more bare-bones though because my blog is less complicated, and I didn’t want to install so many packages.
This is what I ended up with for my src/routes/rss/+server.js with just having to install showdown:
import { fetchMarkdownPosts } from '$lib/utils';
import { readFile } from 'fs/promises';
import showdown from 'showdown';
const siteURL = 'https://vivien.garden';
const siteTitle = 'vivien.garden';
const siteDescription = 'The personal site of Vivien Ngo';
const authorName = 'Vivien Ngo';
export const prerender = true;
export const GET = async () => {
const allPosts = await fetchMarkdownPosts();
const sortedPosts = allPosts.sort((a, b) => new Date(b.date) - new Date(a.date));
// Only get most recent posts so RSS page doesn't get too big
const rssPosts = sortedPosts.slice(0, 10).map(async (post) => {
const postHtml = await getHtmlForPost(post.path);
return {
...post,
content: postHtml
};
});
const resolvedPosts = await Promise.all(rssPosts);
const body = render(resolvedPosts);
const options = {
headers: {
'Cache-Control': 'max-age=0, s-maxage=3600',
'Content-Type': 'application/xml'
}
};
return new Response(body, options);
};
const render = (posts) => {
return `<feed xmlns="http://www.w3.org/2005/Atom">
<id>${siteURL}/blog</id>
<title>${siteTitle}</title>
<subtitle>${siteDescription}</subtitle>
<link href="${siteURL}/rss" rel="self" type="application/rss+xml" />
<updated>${new Date().toISOString()}</updated>
<author>
<name>${authorName}</name>
<uri>${siteURL}</uri>
</author>
<icon>${siteURL}/favicon.ico</icon>
${posts
.map(
(post) => `<entry>
<id>${siteURL}${post.path}</id>
<title>${post.meta.title}</title>
<link href="${siteURL}${post.path}" />
<content type="html">${post.content}</content>
<summary>${post.meta.description}</summary>
<published>${new Date(post.meta.date).toISOString()}</published>
<updated>${new Date(post.meta.update).toISOString()}</updated>
<author>
<name>${authorName}</name>
<uri>${siteURL}</uri>
</author>
</entry>`
)
.join('')}
</feed>
`;
};
const converter = new showdown.Converter();
async function getHtmlForPost(postPath) {
const postMarkdownWithFrontmatter = await readFile(`./src/routes${postPath}.md`, 'utf-8');
const postMarkdown = postMarkdownWithFrontmatter.split('---\n')[2].trim();
let postHtml = converter.makeHtml(postMarkdown);
// prevents HTML in code tags from being rendered
postHtml = postHtml.replaceAll('<', '<').replaceAll('>', '>');
return postHtml;
} Reflections
I don’t have much to say other than “it’s been fun.” Svelte has been fun and refreshing to use, and I feel like I understand a bit more about routing than when I started. I’ve enjoyed working on this site and steadily making adjustments and designing websites that are a bit “weirder” than I might be allowed in a corporate context. Totally recommend making your own site, SvelteKit or no!