For the first time since 2023 I'm available again for new projects! Hire me

Articles

2026

Coding with AI: productivity without pride or joy Apr 01, 2026

I just finished my most productive quarter in a long time, made possible by Claude Code, and there are two conflicting feelings that I want to talk about.

Q1 2026 in review Mar 28, 2026

Normally I just stick to my once-yearly "year in review" articles, but the first three months of this year have been so productive that a Q1 update seems warranted.

Announcing Saga 3 and Parsley 1.2 Mar 26, 2026

Saga 3 brings much faster builds and a more flexible pipeline, while Parsley 1.2 adds powerful Markdown attribute support.

Building modern Django apps with Alpine AJAX, revisited Mar 25, 2026

Nine months after adopting Alpine AJAX with Django, I've gone through template partials, Jinja2, and landed on an approach that's both fast and clean.

The shortcomings of Swift Package Manager Mar 19, 2026

As I prepare Saga 3, I keep running into fundamental limitations in Swift Package Manager that make maintaining a plugin ecosystem unnecessarily painful.

Announcing GetSaga.dev Mar 07, 2026

I've created a brand new documentation website for Saga, built with Saga itself. It features full API reference docs, works without JavaScript, and looks pretty great.

Announcing SwiftTailwind Mar 04, 2026

I've created a Swift package that wraps the Tailwind CSS standalone CLI, removing the need for Node.js or npm in your build pipeline.

Announcing SagaUtils Mar 02, 2026

A collection of reusable utilities for Saga, extracted from this very website: composable HTML transformations and useful String extensions.

Announcing Bonsai, a new HTML minifier in pure Swift Mar 02, 2026

I've created a new HTML minifier in pure Swift, with zero dependencies and performance on par with the best Node.js minifiers.

Protect your analytics with Cloudflare Feb 19, 2026

How I used Cloudflare's free security rules to filter bot traffic from my self-hosted Umami analytics.

Umami vs Plausible: why I switched Feb 18, 2026

After leaving Plausible I moved to Umami. Here's what's better, how I set up proxying to bypass adblockers, and the one problem neither tool can solve.

Self-hosting Plausible broke my analytics Feb 17, 2026

After years on Plausible I switched to self-hosting and discovered just how much they hold back from their open source version.

The software and hardware I use Feb 16, 2026

This is the stuff I use daily for development, photography, and gaming.

Announcing the Saga CLI Feb 14, 2026

Saga now has a companion CLI tool, installable via Homebrew or Mint, with commands to scaffold a new project, build your site, and run a dev server with live reload.

Prevent your Coolify deploys from randomly starting without a build cache Feb 13, 2026

If your Coolify deployments are sometimes fast and sometimes mysteriously slow, Docker's BuildKit garbage collection is probably silently deleting your build cache.

A real-world comparison of static site generators: Hugo vs Publish vs Saga Feb 12, 2026

I built the same site with Hugo, Publish, and Saga to compare how each static site generator handles real-world requirements.

It's time to leave Heroku Feb 07, 2026

Heroku just announced it's entering "sustaining engineering mode". No more new features. After years of security breaches, outages, and price hikes, it's time to leave.

Tim Cook sold Apple's soul Feb 01, 2026

The company I once admired has become morally bankrupt, but leaving the ecosystem feels almost impossible.

Django's test runner is underrated Jan 30, 2026

I never made the switch from unittest to pytest for my Django projects. And after years of building and maintaining Django applications, I still don't feel like I'm missing out.

Announcing Moon, a new HTML syntax highlighter for Swift projects Jan 23, 2026

I've created a new Swift package that does server-side syntax highlighting of HTML content, using Prism.js.

Django 6.0 Tasks: a framework without a worker Jan 19, 2026

Background tasks have always been essential in Django projects. Django 6.0 finally acknowledges that fact, but its new Tasks framework stops short of what real apps need.

Expertise is the art of ignoring Jan 18, 2026

Trying to "master" a programming language is a trap. Real expertise comes from learning what you need, when you need it, and ignoring the rest on purpose.

2025

My Home Assistant setup Dec 27, 2025

Let's dive deeper into my Home Assistant setup. How do I sync everything to Apple's Home app, and how do I automate things.

Home Assistant review after one year of use Dec 26, 2025

Nearly a year ago I replaced a pile of smart-home apps, hubs, and subscriptions with Home Assistant Green. This is my long-term review.

2025 in review Dec 18, 2025

Let's get into personal stuff, freelance work, and open source projects.

Safe Django migrations without server errors Dec 01, 2025

How to run schema-changing Django migrations safely, avoiding schema/code mismatches and server errors during rolling deploys.

How Coolify accidentally broke Docker layer caching (and what you can do now) Nov 15, 2025

A deep dive into Docker layer caching, BuildKit cache mounts, and how a Coolify bug can sabotage your build times, plus what you can (and can't) do about it.

An ode to the tester Nov 15, 2025

Here's to the curious ones. The bug finders. The rebels of the happy path. The round pegs who try every square hole just to see what breaks.

My #1 rule for lasting side project passion Oct 25, 2025

What if the secret to a thriving side project isn't more expertise, but less overlap with your day job?

Async Django: a solution in search of a problem? Oct 21, 2025

While a technical marvel, async Django has been quietly rejected by the community it was built for, with the vast majority of developers sticking to simpler, proven solutions.

Run Django tests using PostgreSQL in GitHub Actions Oct 07, 2025

Did you know that you can run unit tests for your Django app, in GitHub Actions, using PostgreSQL?

Announcing spamusement.cc Sept 30, 2025

Remember the brilliant webcomic Spamusement.com? It ran from 2004 to 2007, but sadly the website went offline in 2020. I missed it so much that I put online an archive.

Django views versus the Zen of Python Sept 14, 2025

Django's generic class-based views often clash with the Zen of Python. Here's why the base View class feels more Pythonic.

One command to run them all Sept 09, 2025

How I use the just command runner to create a simple, unified interface for running, testing, linting, and formatting all my projects, regardless of the tech stack.

A quarter century of chasing simplicity Sept 08, 2025

A reflection on 25 years of web development, from simplicity to complexity and back.

How I write Django views Aug 28, 2025

Why I only use Django's base View class instead of generic class-based views or function-based views.

Announcing django-generic-notifications 1.0.0 Aug 02, 2025

A modern, flexible rewrite of django-generic-notifications is here. Easily send website and email notifications, create digests, group similar messages, and much more.

Automate Python package releases Jul 30, 2025

I maintain a handful of Python packages. Here's how I automate creating new releases, both on PyPI and GitHub.

Why Django's DATETIME_FORMAT ignores you (and how to fix it) Jul 18, 2025

A dive into why Django's DATETIME_FORMAT setting seems to do nothing, and how to actually force the 24-hour clock in the admin, even when your locale says otherwise.

Django at 20: a personal journey through 16 years Jul 16, 2025

Celebrating Django's 20th birthday by looking back at 16 years of personal Django usage, how it evolved, favorite packages, and what I'd love to see in the future.

Handling static and media files in your Django app running on Coolify Jul 08, 2025

Let's solve the challenge of serving media files for your Coolified Django site.

I code in my dreams Jul 04, 2025

I often get my best coding inspiration late at night, and when I go to bed with an unsolved problem, I literally write lines of code in my dreams.

Hosting your Django sites with Coolify Jul 02, 2025

How I moved my Django projects from a manual server setup to Coolify for easier, zero-downtime deployments.

Production-ready cache-busting for Django and Tailwind CSS Jun 26, 2025

I'm a big fan of the django-tailwind-cli package, but I ran into problems deploying it to production. Here's how to make sure you cache-bust tailwind.css.

Liquid Disappointment Jun 25, 2025

Yesterday I installed the iOS 26 beta on my iPhone and today, for the first time ever, I've downgraded my iPhone back to the stable release.

A tale of three type systems: Python, TypeScript, and Swift Jun 19, 2025

I recently ported Saga from Swift to both Python and TypeScript. It was a fascinating exercise in cognitive dissonance, especially when it came to their type systems.

Garbage in, garbage out: why good developers are still necessary in the age of LLMs Jun 18, 2025

Luckily for us, good developers are still necessary in the age of LLMs. You can't just say "make an app", you still need to know how to build a good app.

Make Django show dates and times in the visitor's local timezone Jun 12, 2025

A robust, two-part solution for showing dates and times in your visitor's local timezone, handling the tricky first-visit problem.

Beyond htmx: building modern Django apps with Alpine AJAX Jun 11, 2025

Ditch the complex SPA. Learn how to build modern, server-rendered Django apps using Alpine AJAX and the power of hypermedia.

Thoughts on Apple, and why I left iOS development behind Jun 08, 2025

After more than a decade of iOS development, the company's anti-developer stance, Swift's growing complexity, and the eroding software quality led me back to the open web.

Announcing drf-action-serializers Jun 03, 2025

An easy way to use different serializers for different actions and request methods in Django REST Framework.

Blocking PHP requests using CloudFlare's WAF rules Apr 28, 2025

Webservers get hit by hundreds of thousands of requests to random (non-existing) PHP files. Let's block it using CloudFlare's WAF rules.

You probably don't need a CMS Apr 15, 2025

Many people quickly reach for a big CMS package for Django, when often this is overkill. Here's how to use a simple Django model with a CKEditor 5 WYSIWYG field.

Announcing RSSfilter.com: a Trump filter for RSS feeds, built with Django Apr 06, 2025

I love RSS feeds, but it's not ideal that you're stuck with all the articles that are in the feed. So I built RSSfilter.com, offering a way to filter the feed based on keywords and categories.

Django Admin's handling of dates and times is very confusing Feb 26, 2025

When you have admin users in multiple time zones, the way Django handles the input and display of dates and times is causing confusion. Here's how you can improve things.

uv just keeps on getting better Feb 24, 2025

It's been three months since I migrated all my Python projects over to uv. And it's only gotten better! Let's look at two recent major improvements.

DocC and SPM need some love and attention from Apple Feb 20, 2025

Apple's DocC project and the Swift Package Manager have been missing pretty crucial features for years now. It's time that Apple gave them some love and attention.

Saga... but in Python? Or TypeScript? Feb 05, 2025

What would Saga look like if it were written in Python or TypeScript, rather than in Swift? Is it worth the effort to port Saga to another language?

Looking back at four years of Saga Jan 26, 2025

I started building Saga, my own static site generator written in Swift, four years ago. Let's look at the state of the project.

Looking at Django task runners and queues Jan 24, 2025

I use django-apscheduler to run a queue of scheduled tasks. Now I also need the ability to run one-off tasks and that turned out to not be so simple.

Refactoring Svelte stores to $state runes Jan 18, 2025

One pattern that I love to use in my SvelteKit projects is returning writable stores from the layout's load function. Can we migrate this to the new $state rune?

First thoughts on Svelte 5's runes Jan 02, 2025

I'm migrating a big SvelteKit project to Svelte 5's new runes syntax and I have to be honest: not a big fan of the increased number of lines, especially when it comes to the props.

2024

2024 in review Dec 31, 2025

It's time to look back and see what has changed and what has stayed the same.

Why I still choose Django over Flask or FastAPI Dec 24, 2024

I started using Django in 2009, and fifteen years later I am still a happy user. It's clear that Django is rather special.

Svelte 5 sites don't work as expected in Safari 12 and 13 Dec 03, 2024

Quite recently I upgraded a Svelte 4 project to Svelte 5, and soon afterwards I found some problems inside of Safari 12 and 13 that needed a tricky workaround.

How to migrate your Poetry project to uv Nov 18, 2024

So, like me you've decided to switch from Poetry to uv, and now you're wondering how to actually migrate your pyproject.toml file? You've come to the right place!

Putting Svelte stores inside context for fun and profit Nov 12, 2024

Solving problems by putting writable reactive stores in Svelte's context.

Revisiting uv Nov 11, 2024

Two months ago I compared Poetry with uv, and for me uv had significant drawbacks that kept me from switching over. The situation has changed quite a bit since then!

How to change MEDIA_URL for one FileField Oct 31, 2024

I wanted to use a different MEDIA_URL for one of our FileField instances. It was very easy to do!

Django REST Framework versus Django Ninja Oct 26, 2024

Let's compare Django REST Framework with new kid on the block, Ninja.

Automatically deploy your site when you push the main branch Oct 23, 2024

The best feature of Heroku is the ability to just push a branch, and it gets deployed. How do we replicate a workflow like that on our own server?

Trying out PDM (and comparing it with Poetry and uv) Oct 04, 2024

After comparing uv to Poetry, I am trying out PDM. On paper it combines all the best things of Poetry and uv, without their downsides. How does it hold up?

How I configure my Django projects Oct 02, 2024

There are many ways to configure Django, like multiple settings files or .env files. Here's how I do it, using python-dotenv.

Validate PayPal webhooks using Python Oct 01, 2024

Paypal's documentation only shows a JavaScript example. How do you validate the webhooks in Python though?

Articles now with comments Sept 30, 2024

I've added a comment section to the articles, powered by GitHub Discussions.

Poetry versus uv Sept 17, 2024

Comparing two Python package managers: Poetry and new kid on the block uv.

Changing the way Django 5.1 generates admin list labels Sept 16, 2024

Django 5.1 adds related field lookup to the model admin's list_display, but with an annoying quirk. Let's fix that!

Extend Django's autocomplete widget actions Jul 22, 2024

Extending Django's autocomplete widget with a new action which copies the linked user's email address to the clipboard.

SvelteKit architecture tip: return a writable store from your load function Jul 20, 2024

How do you update content in real time when that content was fetched from the layout's load function?

Hardening a web server against script kiddies Jun 09, 2024

Webservers get hit by hundreds of thousands of requests to random (non-existing) PHP files. Let's block them using UFW and fail2ban.

Quick review: 12.9" iPad Pro (2018) Jan 23, 2024

I bought the 12.9" iPad Pro with the Apple Pencil back in 2018 and spoiler alert: I still use the iPad almost every day.

My one week with a Tesla Jan 14, 2024

I rented a Tesla model 3 in Iceland, and let's just say I have some opinions.

2023

2022

2021

2021 in review Dec 29, 2022

Another year of not going abroad, barely seeing friends, playing Dungeons & Dragons via Zoom instead of at the table. But also a year of hope.

Working around HttpOnly cookie problems in SvelteKit Dec 25, 2021

When HttpOnly cookies didn't work as expected in my SvelteKit project I had to find a workaround.

Saga 1.0.0 has been released Dec 12, 2021

It's taken a little bit longer than I expected, but 1.0.0 has finally been released, with support for async readers and item processing functions.

Vapor 4 versus Django REST Framework Aug 26, 2021

Over two years ago I wrote an article where I compared Vapor 3 to Django REST Framework. It's time for a rematch with Vapor 4.

Architecting a SvelteKit app - and failing Aug 15, 2021

Trying to architect a SvelteKit app so that it does as few requests as possible, from a central place, so that all subpages have access to the content.

My one big complaint working with Vapor 4 Jul 17, 2021

I'm trying out Vapor 4 for a side project, and one thing that I am constantly running into is the amount of boilerplate and copy-pasted code. Are there no better solutions for this?

Interview with proglib.io Jul 11, 2021

Recently I was interviewed for the Russian IT website proglib.io. Since it might be interesting for non-Russian speakers, here it is in the original English version.

Mentee Question 5: What's the deal with coordinators? Mar 10, 2021

One of my mentees asked about the coordinator pattern: how to implement it, and what the big deal is about decoupling view controllers.

Mentee Question 4: When to use PassthroughSubject and CurrentValueSubject? Feb 25, 2021

It's a question I asked myself too, when I just got started with Combine.

Mentee Question 3: How to know when multiple publishers completed? Feb 22, 2021

In JavaScript-world, it's really easy to know when multiple promises completed: just use Promise.all. How do you do the same thing in Combine?

Announcing tag-changelog: automate your changelog and GitHub releases Feb 20, 2021

I've released a GitHub Action that automatically generates a changelog when you push a tag.

A review of Markdown parsers for Swift Feb 17, 2021

Comparing Parsley to Ink, Down, and MarkdownKit.

Building my own static site generator, part 7: updates & the road to 1.0.0 Feb 13, 2021

In the past few days I've made some pretty substantial improvements to Saga, to make it work for me and my website, which is now built using Saga.

Building my own static site generator, part 6: replacing SwiftMarkdown Feb 09, 2021

I've already replaced my own SwiftMarkdown package...

Building my own static site generator, part 5: replacing Ink and Splash Feb 08, 2021

I've replaced the Ink and Splash dependencies with my own SwiftMarkdown package.

Building my own static site generator, part 4: a complete redesign Feb 03, 2021

An unexpectedly quick fourth article about Saga, after a complete redesign of the API.

Building my own static site generator, part 3: thoughts so far Feb 02, 2021

In the third and final part of this series about Saga I'm looking at the pros and cons of the current system and what I might want to change.

Building my own static site generator, part 2: API design Jan 31, 2021

Part 2, where I'm looking back at the current API of Saga.

Building my own static site generator, part 1: inspiration & goals Jan 30, 2021

In part 1 of a series of articles I'm looking at the inspiration behind my static site generator Saga, now available on Github.

Swift generics and arrays Jan 27, 2021

Working with generics in Swift becomes a headache when you want to put things in an array. Am I stuck with type erasure and type casting?

Looking at the static site generator Publish Jan 22, 2021

I'm taking a look at the static site generator Publish, written in Swift.

Mentee Question 2: How to get started Jan 21, 2021

Resources for learning Swift and UIKit, what to build first, opinions on Unit Testing, and more.

Mentee Question 1: UIKit or SwiftUI Jan 20, 2021

My take on the very common question "What should I learn or focus on? UIKit or SwiftUI?"

2020

Book review: Thinking in SwiftUI Sept 19, 2020

A while ago I asked on Twitter which Swift-related book I should review next, and overwhelmingly Thinking in SwiftUI by the objc.io guys was chosen. An excellent choice!

Exploring two-way databinding solutions in UIKit Jul 10, 2020

SwiftUI's @Binding property wrapper makes it easy to create two-way databindings, but in the UIKit world it's slightly less easy. Let's explore some solutions.

Book review: Practical Combine Jun 29, 2020

Great book, and well worth the $25.

Connecting Stripe to Firestore via Cloud Functions and webhooks Jun 26, 2020

Since it was a bit of a puzzle to get it working, I am sharing my backend and frontend code.

A review of SwiftUI problems Jun 21, 2020

I've been working with SwiftUI for almost half a year now I love a lot about it, but there are also so many bugs and issues that need workarounds that it's kind of maddening.

User subscriptions on the web Jun 20, 2020

Comparing Stripe, Gumroad, Patreon, and Memberstack.

Connecting Storekit to Firestore via Cloud Functions and webhooks Jun 15, 2020

I've recently added subscriptions to my Critical Notes iOS app, using Apple's StoreKit. Here is how I hooked it all up to Firestore including server-side receipt validation.

Clean up Firestore and Storage when deleting a document Jun 14, 2020

When you delete a document in Firestore, its subcollections and their documents are not automatically recursively deleted. Here is a simple Cloud Function that takes care of it.

After Vapor and Django comes.. Firestore Jun 07, 2020

I couldn't choose between Vapor and Django, and went with a third option.

2019

2016

2015

2013

2012

2011

2010

2009