simon@letsbuild 2025-03-01-simplest-static-site-generator.html utf-8

The world's simplest Static Site Generator in Go

Simon's Blog — LetsBuild.cloud

I was bored on a Saturday afternoon, and thought that my personal site could do with some simplification. In the past I have used static site generators such as Astro and Jekyl. But these were surplus to needs, and instead I thought it’s time to do the Software Engineer thing and re-invent the wheel.

All I needed was a script that traversed a directory of markdown files, converted them to html, and then inserted them into a simple HTML template. It turns out that this is very simple code to write, and can be done in a few hundred lines of Golang, or JS, or <pick your poison>.

In my case, the processMarkdownFile is the core of this, converting a file of markdown into html and injecting it into a template html file:

1// processMarkdownFile processes a single markdown file and returns the generated HTML
2func processMarkdownFile(filePath, template string) (string, string, *BlogPost, error) {
3 // Parse frontmatter
4 var meta FrontMatter
5 content, err := frontmatter.Parse(strings.NewReader(string(fileContent)), &meta)
6
7 // Parse markdown to HTML
8 htmlContent := markdown.ToHTML(content, nil, nil)
9
10 // Replace template placeholders
11 output := strings.Replace(template, "{{title}}", title, -1)
12 output = strings.Replace(output, "{{content}}", string(htmlContent), -1)
13
14 // ...rest of the function...
15}

The other need was for an index page to list all my posts. This can be done by sorting and slicing them in date descending order, which I extracted from the standardised file name format ([yyyy-mm-dd]-[title-bits].md):

1// Sort posts by date in descending order (newest first)
2sort.Slice(posts, func(i, j int) bool {
3 return posts[i].Date.After(posts[j].Date)
4})

Generating the post list:

1for _, post := range posts {
2 if post.Date.IsZero() {
3 continue // Skip posts without dates
4 }
5
6 formattedDate := post.Date.Format("January 2, 2006")
7 contentBuilder.WriteString(fmt.Sprintf("<li><strong>%s</strong> - <a href=\"%s\">%s</a><p>%s</p></li>\n",
8 formattedDate, post.OutputFile, post.Title, post.Description))
9}

Supporting frontmatter is handy for metadata, but not absolutely essential:

1---
2title: My Custom Page Title
3description: A brief description of the page content
4---

Parsed with:

1content, err := frontmatter.Parse(strings.NewReader(string(fileContent)), &meta)

For more details, checkout the README and source code in the repo.