I’d been hosting websites mainly with WordPress. But did I really want to go down that road again?
Why I ditched WordPress
For a simple blog, WordPress just comes with way too much overhead:
- PHP required
- MySQL database required
- ships with an API
- admin backend with login
- extensive .htaccess config to secure the site
- plus plugins for everything:
- Multilanguage
- Image Optimizer
- Caching
- SEO
- Contact forms
- Search
- Security plugins to patch the vulnerabilities introduced by other plugins
- etc.
All of that needs to be maintained, updated regularly, and tweaked constantly. Every third update breaks something else and you end up hunting for fixes and workarounds.
I had no interest in any of that, so I started looking for alternatives.
Static site generators as an alternative
I was already familiar with static site generators like Hugo from GitLab / GitHub Pages — but I’d never actually worked with one.
So I looked at Hugo and a few other tools along with their themes, and eventually landed on Astro and the relatively new Velocity theme by Southwellmedia. Why Astro over Hugo? Honestly, I can’t say exactly. I just liked the demo themes better. Technically and in terms of workflow, the two are pretty similar.
Astro & the Velocity theme
Both are fully open source and tick all my boxes:
- no CMS with a backend
- no database required
- multilanguage support
- easy to customize
- built-in image optimization
- SEO
- search
- small and fast
The theme already ships with Astro’s i18n support built in, so I didn’t have to wire that up myself. The demo site scores 100/100 in Lighthouse.
I only had to make some design changes and adapt a few features to my needs. With the solid documentation, a lot of trial & error, and some help from Claude, that was doable in a few evenings 😅
Features I added on top:
- Copy button in code blocks (rehype plugin)
- Reading progress bar
- Reading time
- Prev/Next navigation between posts
- Typewriter effect on the homepage
- Language toggle with browser language detection and cookie storage
Once the foundation is in place, adding content is straightforward. Posts are just written in Markdown:
---
title: "Test Post"
description: "Post description"
publishedAt: 2026-04-01
updatedAt: 2026-04-15 # optional
author: "Tobi"
image: ./image.jpg # optional
imageAlt: "Image description" # optional
tags: # optional
- astro
- tutorial
draft: true # optional
featured: false # optional
locale: en # optional
---
# start writing in markdown here.At build time, everything gets rendered to HTML and the output folder contains a simple static site structure that can be served by any web server.
Quickstart: Setting up the project
The Velocity theme uses pnpm as its package manager. pnpm can be installed via npm:
npm install -g pnpm@latestCreating the demo project from the Quick Start is straightforward:
- Create the repo using the Velocity CLI.
pnpm create velocity-astro my-project- Switch into the project directory and start the dev server.
cd my-project
pnpm devThe demo site is then available in your browser at http://localhost:4321/.
For the production build, you’ll need to create the env file and configure the site URL:
cp .env.example .envSet the variable accordingly, e.g.:
SITE_URL=https://homelabdiary.devThen run the build:
pnpm buildThe dist folder will contain the generated site structure, ready to be served by any web server.
If the following error occurs during the build, the astro.config.mjs file in the demo project is incorrect because the environment variables are not exported properly. In this case, you must download the corrected file from the Velocity GitHub project.
[ERROR] [vite] ✗ Build failed in 1.60s src/config/site.config.ts (1
): “SITE_URL” is not exported by “astro/server”, imported by “src/config/site.config.ts”. file: C:/temp/my-project/src/config/site.config.ts:1 1: import { SITE_URL, GOOGLE_SITE_VERIFICATION, BING_SITE_VERIFICATION } from ‘astro/server’;
Hosting with Cloudflare Pages
I’m hosting my blog on the free plan of Cloudflare Pages. There are two ways to publish a site via Cloudflare Pages:
-
You can connect Cloudflare directly to a GitHub or GitLab repository. When you push to the main branch, Cloudflare handles the build and serves the output. The free plan includes 500 builds per month — details here.
-
Alternatively, you can run the build yourself and either upload the output manually via the Cloudflare dashboard or deploy it automatically using Wrangler.
I went with option two, since I self-host my own Forgejo server and runner at home.
On every push to the main branch, a Forgejo Action (compatible with GitHub Actions) runs, builds the project, and deploys it to Cloudflare.
The deploy.yml for the workflow looks like this:
name: Deploy to Cloudflare Pages
on:
push:
branches:
- main
jobs:
deploy:
runs-on: debian-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: latest
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm build
env:
SITE_URL: https://homelabdiary.dev
- name: Deploy to Cloudflare Pages
run: npx wrangler pages deploy dist --project-name=homelabdiary --branch=main
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}The two variables CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN are stored as secrets in the repository.
Publishing a new post
Whenever I want to publish a new post, the workflow is pretty simple:
- create a new Markdown file in the right directory and fill it with content
git add --allgit commit -m 'new post'git push origin main
The project then builds automatically, gets deployed to Cloudflare, and goes live at https://homelabdiary.dev.
That’s exactly the workflow I was after: lean, low-maintenance, and (mostly) without surprises. ♥️