mlauwers

Site architecture

This is some documentation on how this site was build. It serves both as a (hopefully) interesting blog post and as documentation to myself in case I don't touch it in the next year.

Architecture diagram

Tech stack

  • Netlify (hosting)

  • Astro (web framework)

  • Svelte (client side UI framework)

  • Storyblok (CMS)

  • Neon (cloud based SQL database)

  • Umami (analytics)

All platforms are integrated using the free tiers. This works out because the site is read only with few content updates and can thus be cached rather aggressively.

Netlify

Netlify hosts the site and has first class support for Astro. There is deploy logging, runtime logging of the Astro SSR function, environment variables management and a web hook to trigger rebuilds. So far I haven’t run into any problems or limits.

Astro

Astro is the web framework of choice. The intention was to create static content pages and have all content pulled at build time. However because of having basic HTTP auth in middleware during development Astro forces pages to be dynamic. So in the end all pages are implemented using SSR with cache validation. I tested switching back to static pages after removing basic auth, but decided it's more interesting in terms of learnings to keep them dynamic.

There is a catch all .astro slug file for publicly available pages. Admin pages have dedicated .astro files.

An API route /invalidate-page is set up that can be called to invalidate the page cache for any given page slug.

Svelte

Svelte is only used for a few interactive client side components such as the light/dark toggle, the books list and a sortable books table to manage the list which is only used on the admin pages.

Storyblok

Storyblok serves as the CMS and contains the structure and content of all public pages. There is a content blok mapping for components such as headers, images, rich text, etc. with placeholder components for the books list and the notes overview.

Each public page lives as a content page in Storyblok. On page load Astro fetches the related page content using the Storyblok SDK and constructs the page. The following cache headers are set:

  • Cache-Control: set to must-revalidate

  • Netlify-CDN-Cache-Control: set to seven day stale-while-revalidate

  • Cache-Tag: set to the page slug so we can later invalidate the page cache with the /invalidate-page API.

Whenever a page gets published, Storyblok calls our /invalidate-page webhook with the edited page slug to make sure that any old cached version gets invalidated. To do so, Netlify’s purgeCache function is called which invalidates all caches based on the Cache-Tag we set. If enabled (via an environment variable) this webhook will also trigger a new Netlify build which would be required to update the static site version.

Postgres

A Postgres SQL database with a single books table is used to manage the list of read books. The table is initially populated through a utility script that imports a CSV exported from Goodreads and can subsequently be updated through the admin pages.

I was thinking about embedding a SQLite file with the contents but that would make it harder to add a read book using the site as any rebuild would re-bundle the original database file that lives in Git. That said maybe there is a way to tell Netlify to skip certain files on future deploys. For now though the db is cloud based via Neon.

Yet another alternative could have been to use Google’s free Firestore database and store each book as a JSON entry inside a Books collection but I really like the simplicity of a SQL table for this one.

Umami

Umami is integrated through a simple lightweight (1.8 kB) import script and automatically captures anonymous and basic analytics such as the number of visitors per page and their country of origin. Because Umami is GDPR-compliant and doesn’t set tracking cookies there is no need for a cookie banner.