Alexander Schaaf

Colophon

This website is built using Hugo, a fairly fast static site generator. It takes care of converting my writing in Markdown into HTML based on custom templates. I wrote some custom CSS for the styling, trying to keep it as minimal as possible. Otherwise I lose myself in fiddling with meaningless design choices, rather than do the thing this website was created for: writing and photos.

This is very much inspired by the approach of boring technology – with the goal to make this website as easy to maintain and deploy for (potentially) the rest of my life. Hugo is of course not a boring technology, and j3s makes a great point about this in their article. But if Hugo ever goes to shit and becomes unmaintained and unusable, I can easily plug my markdown content into any other static site generator – or even write my own.

Hosting & Deployment

The non-image content is hosted using the Cloudflare Pages free-tier. I’m using their Wrangler CLI to deploy changes manually using a little Makefile.

Images

The images are hosted on Cloudflare R2, which is a zero egress fee object storage - similar to AWS S3. rclone comes in to pull and push images from and to the R2 bucket.

I went a bit overkill to provide four different sizes for each image on this website. This is done in response to the clients display size, to not overfetch large images (with longer load times) on devices that don’t need them (and that are more likely to be on a metered connecton).

Hugo’s shortcodes provide a great way to add custom snippets inside markdown content. After scouring over some blog posts I managed to cobble together this neat little shortcode to display responsive images:

{{<post-image image="img/path.jpg">}}
  The image caption
{{</post-image>}}

This takes the full sized image at img/path.jpg and automatically generates the four different image sizes during compile time. The HTML resulting then displays the correctly sized image based on the clients viewport size by using different source elements withing a picture element.

Here’s the shortcode:

{{ $image := (.Page.Resources.GetMatch  (index .Params.image)) }}
{{ $alt := .Get "alt" }}
{{ $width := .Get "width" }}
{{ $borderless := .Get "borderless" }}
{{ $src := $image }}

{{ $imageCdn := site.Params.imageCdn }}
{{ $src := $image }}

{{ $largest := $image }}
{{ $aspectRatio := div $image.Height $image.Width }}
{{ $isPortrait := ge $aspectRatio 1 }}

<picture>
  {{ $sizes := slice
    (dict "width" "500" "media" "(max-width: 500px)")
    (dict "width" "800" "media" "(max-width: 800px)")
    (dict "width" "1200" "media" "(max-width: 1200px)")
  }}
  {{ range $sizes }}
    {{ if ge $image.Width .width }}
      {{ $resizedImage := $image.Resize (print .width "x webp q90") }}
      {{ if hugo.IsProduction }}
        <source media="{{ .media }}" srcset="{{ print $imageCdn $resizedImage.RelPermalink }}">
      {{ else }}
        <source media="{{ .media }}" srcset="{{ $resizedImage.RelPermalink }}">
      {{ end }}
      {{ $largest = $resizedImage }}
    {{ end }}
  {{ end }}
  {{ if .Inner }}
    <img
      src="{{ if hugo.IsProduction }}{{ $imageCdn }}{{ end }}{{ $largest.RelPermalink }}"
      class="{{ if $isPortrait }}portrait{{ else }}landscape{{ end }}"
    >
    <figcaption>
      <em>{{ .Inner }}</em>
    </figcaption>
  {{ end }}
</picture>