hauskeeping

Very much improved image loading for Micro.blog

A few weeks ago I built a little hack for image loading on Micro.blog. The goal was to serve multiple image sizes to allow browsers to decide which version to load based on screen size, pixel density, and, if supported, even network conditions. The problem was, that image loading somehow always felt a bit janky in my tests.

Inspecting the page didn’t reveal anything obvious, so I asked ChatGPT for help. The real issue turned out to be caching, or rather, the lack of it. Because I had encoded the target URL with urlquery, the image paths varied slightly and the browser never reused them from cache. While we were at it, I also got a few suggestions for further improvements. I dropped the 1200px variant since my content column is never wider than 640px, reworked the hook so image URLs are now consistent, and added a few extras like cleaner srcset/sizes and LCP prioritization for the first image.

The pages now feel noticeably smoother, images appear instantly, and they still only use as much bandwidth as needed. Sometimes it really pays off to take a closer look at your own site and sweat the details. Here is the updated render-image.html.

{{ $dest := .Destination | safeURL }}
{{ $alt  := .Text | default "" }}

{{ $abs := cond (hasPrefix $dest "http") $dest ($dest | absURL) }}
{{ $isMB := or (hasPrefix $abs "https://micro.blog/photos/") (hasPrefix $abs "http://micro.blog/photos/") }}

{{ $src300 := "" }}
{{ $src600 := "" }}
{{ $src900 := "" }}

{{ if $isMB }}
  {{ $src300  = (replaceRE `/photos/[^/]+/` "/photos/300x/"  $abs) }}
  {{ $src600  = (replaceRE `/photos/[^/]+/` "/photos/600x/"  $abs) }}
  {{ $src900  = (replaceRE `/photos/[^/]+/` "/photos/900x/"  $abs) }}
{{ else }}
  {{ $encoded := $abs | urlquery }}
  {{ $src300  = printf "https://micro.blog/photos/300x/%s"  $encoded }}
  {{ $src600  = printf "https://micro.blog/photos/600x/%s"  $encoded }}
  {{ $src900  = printf "https://micro.blog/photos/900x/%s"  $encoded }}
{{ end }}

{{ $isFirst := not (.Page.Scratch.Get "lcp_done") }}
{{ if $isFirst }}{{ .Page.Scratch.Set "lcp_done" true }}{{ end }}

<img {{ if $isFirst }}loading="eager" fetchpriority="high"{{ else }}loading="eager" fetchpriority="auto"{{ end }}
  decoding="async"
  src="{{ $src600 }}"
  srcset="{{ $src300 }} 300w, {{ $src600 }} 600w, {{ $src900 }} 900w"
  sizes="(max-width: 480px) 100vw,
         (max-width: 768px) 80vw,
         640px"
  alt="{{ $alt }}">

Frischer Anstrich

Ich habe meinem Blog übers Wochenende einen neuen Anstrich verpasst, nachdem ich mit dem bisherigen Theme zu viele Kompromisse eingehen musste und viel zu oft tief ins CSS gegriffen habe. Jetzt strahlt hier das mnml-Theme von Jim Mitchell, mit einem klaren, reduzierten Design und vielen direkt eingebauten Features. Ich musste tatsächlich nur noch wenige Kleinigkeiten anpassen.

Neben dem deutlich aufgeräumteren Look, gibt es auch einen überarbeiteten Dark-Mode, eine aktualisierte Fotogalerie sowie ein neues Favicon. Darüber hinaus habe ich die Über-Seite entschlackt und die 404-Seite überarbeitet, für die Sora mir auf meinen Wunsch ein kotzendes Einhorn gezeichnet hat.

Unter der Haube verrichtet natürlich weiterhin das Hugo-basierte Micro.blog seine Dienste.

Ein cartoonartiges Einhorn spuckt einen Regenbogen, begleitet von einem humorvollen Entschuldigungstext auf einer Webseite.

Smarter image loading for Micro.blog

I just updated my Micro.blog theme to deliver images in a more efficient and responsive way. This means faster page loads, lower data usage, and no more compromises on image quality.

The trick? A clever use of Hugo’s Markdown render hooks, which Micro.blog supports via its theme templates. Shoutout to this helpful thread on the Micro.blog Help Forum, which showed me how to combine srcset, sizes, and Hugo templating to give the browser more flexibility.

Instead of serving a single fixed-size image, I now provide multiple image sizes. This allows the browser to decide which version to load based on screen size, pixel density, and, if supported, even network conditions.

Here is how it works

Create a file at layouts/_default/_markup/render-image.html in your Micro.blog theme with the following code:

{{ $path := .Destination }}
<img
  src="https://micro.blog/photos/600x/{{ $path }}"
  srcset="
    https://micro.blog/photos/300x/{{ $path }} 300w,
    https://micro.blog/photos/600x/{{ $path }} 600w,
    https://micro.blog/photos/900x/{{ $path }} 900w,
    https://micro.blog/photos/1200x/{{ $path }} 1200w"
  ...
/>

What it does:

Previously, I used a bunch of iOS Shortcuts and external tools to downscale images before uploading them, which worked, but was tedious. Now, that extra workflow is no longer necessary. I can upload the full image and let Hugo and the browser do the rest.

The caveat is that most of my older posts won’t benefit from this. To make them compatible, I would have to swap in the original high-resolution files and replace all HTML-based image embeds with Markdown. That is unlikely to happen, unless I’m really bored someday. Nevertheless, it’s a small change with a big impact going forward, and I love it.

Update · 25/08/2025

I noticed a caching issue with my first implementation: images were constantly reloading instead of being served from the browser cache. The culprit was the unnecessary use of urlquery, which produced slightly different URLs on each render. I removed it, and now caching works as expected, images load instantly once they’ve been fetched the first time.

Dank Micro.blog habe ich jetzt mein Mastodon-Profil auf den Feediverse-Account von meinem Blog umgezogen und bin ab sofort unter @[email protected] erreichbar. Damit habe ich jetzt die volle Kontrolle über meinen Content und kann über ein Profil und eine App auf alles zugreifen.