As my prefrontal lobe slowly approach its full development, I purchased a domain name.
melvil.fr, my name followed by my country code top-level domain (ccTLD).
This blog post will detail how I set up my VPS to serve this website and what I learned.
Tech stack
The tech stack will probably evolve.
The server side is handled by a golang application, serving static html composed from template by html/template using net/http.
No library or framework is used on the fronted for now, just pure html/css/javascript.
The blog posts are written in markdown, stored and versioned in a separated repository.
At initialization (and on endpoint trigger), the markdown files are converted to HTML using gomarkdown.
This golang application runs with docker compose and is accessed through a traefik.
CI/CD is handled with Github Action and ssh as following:
- For golang application, at every push on main the image is built (using the simplest Dockerfile possible). Then, the github action container ssh inside the vps, pull the new image and finally relaunches docker compose.
- For the blog posts, at every push on main a github action
scpmy files to the mount point of the golang application Then, the blog post recomputation HTTP endpoint is called.
Underlying philosophy: Why those choice ?
I grew tired of the never-ending flow of new technology and framework. As I start programming not so long ago (about 4 years now), and started with frontend application development, I follow a “top to bottom” route, starting with higher abstaction frameworks and libraries, to go down to more and more low level concept. This made me realized that even if it resulted in a longer development time, it was a happier one, spent running at an ecstatic pace in the field of tailored code, with just the right level of abstraction that I need, written and maintained by myself with reduced surface area with others’ less-crappy-but-still-crapy libraries, because I can’t stand seeing the promise of an easy developer experience collapse into hours of debugging, just to realised that the concerned use case is not handled and need hundreds lines of code to get around.
This is why (and without more thinking and benchmarking to be honest) that I chose golang for server code. Indeed, in a previous job I got my hand on go and had a really great time with it. The extensive standard library provides a single source of truth for various general use cases without too much constraint. This is also why I chose to not use any frontend library. Reject reactvue&co, embrass good ol’ htmlcssjs.
For CI/CD and deployment I just went with facility, nothing more to say.
Things I learned
Go concurrency
As I come from a frontend/graphical programmation background, I did not mess with parrallel code execution concept that much yet.
The golang net/http server mechanism creates a concurrent goroutine for every tcp connection.
As I wanted to reduce file i/o, I collect all the templates and blog posts at initialization. The templates files do not changed without a new deployment, but, as it is a different repository sync on its own, I needed a way to recompute their content without desynchronization.
The way I have done it is with a RWMutex, that blocks all the requests during the blog posts recomputation.
SEO and performance
I absolutely want a max lighthouse score as it is a really simple website that I control from end-to-end. Yes this is absolutely an ego thing. In this quest, I discovered that lighthouse (which seems to struggle on wayland) redirected me to some really insightfull article. Few of the knowledge I found:
- all those meta tags in html head are quite important and also quite dope. It customized search-engine result description (meta name=“description”), social network share icon & text (meta propriety=“og:…”) etc
- caching static files using headers improves SEO
- nerding out on font loading and display allows reducing LCP (Largest Contentful Paint) and CLS (Cumulative Layout Shift) by displaying a fallback system font until fetched font load, then swapping it. This is how I had done it:
@font-face {
font-family: 'Geist';
font-display: swap;
src: url('/static/fonts/geist.ttf') format('truetype'),
url('/static/fonts/geist.woff2') format('woff2'),
url(https://fonts.gstatic.com/s/geistmono/v4/or3nQ6H-1_WfwkMZI_qYFrcdmg.woff2) format('woff2');
font-weight: normal;
font-style: normal;
}
Overall I figure out that constaint put by SEO best practice are, in fact, a lot of cool features that I would probably have overlooked otherwise.