Setup

2024-05-31Updated on 2026-03-31

This post walks through how I built and customized my homepage.

Base Setup

A few months ago, I stumbled over the small universe of static site generators. They are commonly used by academics to produce personal websites, mostly using Jekyll (a static site generator) and academicpages (a theme of Jekyll).

I chose to go on another path by using Zola, another static site generator. Besides having the benefits of being a static site generator (no databases one needs to worry about, better performance, easy transferability of posts etc.) it is also considerably faster and smaller in size compared to Jekyll.

To make everything look nice, I went with serene. Next to having a nice overall look & feel, one can insert math with KaTeX and it also has a built-in dark mode.

Theme customizations

For modifications, I mainly adjusted the theme according to usage.md (e.g., using a different color scheme, modifying the homepage). As I don't like spam in my inbox, I added some javascript code to hide my e-mail address from webscrapers:

templates/home.html
    <SCRIPT type="text/javascript">
        emailE = 'example.com'
        emailE = ('mail' + '@' + emailE)
        document.write('<a href="mailto:' + emailE + '">' + '{% set icon_path = "static/icon/email.svg" %}' + '{% set icon = load_data(path=icon_path) %}' + '{{ icon | safe }}' + '</a>')
    </script>
The new color scheme didn't fit well with the default syntax highlighting colors so instead I implemented a slightly modified version of onehalf.

Open Graph Compatibility

When you share a link on social media or messaging apps, the preview card that appears (title, description, image) is powered by the Open Graph protocol. Adding support for this turned out to be more involved than expected. I had tried and failed to do this manually before, so I let Claude Code handle it.

The meta tags themselves are straightforward: og:title, og:description, og:image, and a few more, all injected via a Tera template partial (_head_extend.html) that covers every page type (posts, sections, homepage). For the template structure, we drew inspiration from the zola-zap theme's macro-based approach and Jordan Glass's OG implementation.

The trickier part is the OG image. Rather than manually creating a card for each post, Claude Code wrote a small Node.js script (scripts/generate-og-images.mjs) that runs at build time, before zola build. It reads each post's frontmatter, renders a 1200x630 card using satori (HTML/CSS to SVG) and resvg-js (SVG to PNG), and drops the result as a colocated asset next to the post's index.md. Zola then picks it up automatically. The idea of generating images at build time rather than at request time (via Cloudflare Workers) was informed by this blog post -- it uses Rust, but satori + resvg turned out to be simpler in this case. On Cloudflare Pages, this is wired into the build command so new posts get their OG images without any manual step.

Plotly

Plotly.js support is opt-in per page. Setting plotly = true in a post's [extra] frontmatter loads the library from a CDN, keeping the ~4 MB bundle off pages that don't need it. Plotly's default styling clashes with the site's custom color scheme, so charts use transparent backgrounds, theme-aware font/grid colors (switching on the dark class), and a MutationObserver to re-render when the user toggles dark mode.