Why I built this blog
The exposing-guidongui repository contains the tech stack of my personal blog, reachable at exposing.guidongui.com. As the subdomain name suggests, I'm not seeking fame, just a public place to talk about my personal projects. Public exposure is a way to do better what I do every day and above all a way to finish the small projects I start and then very often leave in POC state.
I've always wanted a space where I could pour out my ideas, but for deontological reasons, I've always refused to use CMS like Hugo to build one. The day I would do it, it would be built from scratch by me so I could learn something new. The goal is not to look pretty or smart to the world, but to learn by experimenting. I preferred simplicity over flashiness, custom Go libraries over frameworks.
The blog is completely self-developed, self-hosted in my Home Media Lab, which I hope I'll have the chance to talk about in future posts.
From the README:
net/http Standard Library
The server uses Go's standard
net/httppackage without external routing frameworks.Key patterns:
- Uses Go 1.22+ method-based routing (
GET /path) - Path parameters extracted via
r.PathValue("id") - Handlers return
http.HandlerFuncclosures with dependency injection for readers http.FileServerserves static CSS and JS fromweb/templates/
Go Templates
Templates use the text/template package with .gohtml files.
Homepage template (home.gohtml) iterates over posts:
<article>
<h3><a href="{{.URL}}">{{.Title}}</a></h3>
<address>{{.Date}}</address>
<pre>{{.Summary}}</pre>
</article>
Post template (post.gohtml) renders single post data:
<title>{{.Title}}</title>
<pre>{{.Content}}</pre>
Data structures passed to templates:
type HomepageData struct {
Posts []PostData
}
type PostData struct {
Title, Author, URL, Date, Summary, Content string
ViewCount int
}
Templates are parsed once at handler creation and executed per-request via tpl.Execute(w, data).
Goldmark Markdown Parsing
Goldmark converts Markdown posts to HTML.
mdConverter := goldmark.New(
goldmark.WithExtensions(extension.GFM), // GitHub Flavored Markdown
)
var buf bytes.Buffer
mdConverter.Convert(mdPost, &buf)
pd.Content = buf.String()
The GFM extension enables:
- Tables
- Strikethrough (
~~text~~) - Task lists (
- [x] done) - Autolinks
Custom Metadata Parser
Posts use a custom metadata format at the file start:
Asciinema Integration
The homepage displays a terminal recording via a self-hosted Asciinema server.
Frontend integration (home.gohtml):
AsciinemaPlayer.create(
'https://asciinema.guidongui.com/a/34.cast',
document.getElementById('asciinema-player'),
{ autoPlay: true, loop: true, theme: 'white', ... }
);
Self-hosted server (deploy/asciinema/deployment.yaml):
- Runs the official
ghcr.io/asciinema/asciinema-serverimage - PostgreSQL database in a sidecar container
- Exposed at
asciinema.guidongui.comvia Traefik IngressRoute with TLS - Configuration: signups disabled, uploads require auth, public recordings