Below is an overview of how this simple app is made and what technologies are used. If you'd like to dive straight in, the full project is available on my Gitea .
This app was originally made using .NET Blazor WASM and then re-written in React with TypeScript as a learning exercise. I've now migrated it back to .NET Blazor to take advantage of SSR (static server-side rendering) for maximum performance and to remove unnecessary dependencies on large JS and CSS libraries.
Although SSR sacrifices client-side interactivity, for simple sites like this it means that page loads are practically instant and appear to function similarly to SPAs (the browser doesn't reload on navigations, new content is simply swapped in) but without the initial large download of the full application and it's dependencies.
This version uses pure vanilla CSS with CSS variables for theming, eliminating all JavaScript and external dependencies; the mobile menu uses a CSS checkbox hack for zero-JavaScript interactivity.
When this app was written in Blazor WASM and then React (both client-side-only technologies) it was hosted on an Azure Static Web App and deployed via GitHub Actions. This provided an easy and cost effective way to get something out there without worrying too much about the infrastructure. With this being a personal project I needed to keep the costs minimal and even though I would've preferred a server to work with there wasn't many free options.
I've always been a bit of a networking/home-lab/server nerd so as soon as Cloudflare announced their
Tunnels
feature I decided to revisit this site, rebuild in a server-side technology and host it myself on server I setup at home running a headless linux server operating system. I configured the server with
Docker
and
Docker Compose, built a Docker container for the .NET app to run from, copied it to my server and created a docker-compose.yml
file to run it with the Cloudflare Tunnel
Docker image.
After ensuring the app was working correctly I decided to migrate the code repo from GitHub to a locally hosted
Gitea
instance running on the same server. I also setup a self-hosted runner and created a
build and deploy workflow
that creates a new docker image, copies it to the app server, runs the docker-compose.yml file and busts the Cloudflare cache to ensure a clean deployment when new changes are pushed to the main branch.