← Blog
Go · Tooling · Blogging · Developer Tools2026-05-20 . 4 min read

My Blog Broke. So I Built a Tool to Prevent That.

I built a small Go CLI tool this week — blogtools. I write my posts using markdown files. Each one contains a frontmatter block — metadata placed at the very top of the file that tells my blog the post title, date, tags, and reading time.

--- 
title: "Hello."
date: "2026-05-17"
tags: ["Personal"]
excerpt: "Blogging is something I always wanted to try, and I finally got the courage to start."
readingTime: "2 min read"
---

While writing my first blog and testing it locally. I forgot some fields and the website broke.

That's where i got the first idea of building it. And it seems the right idea to prioritize building it, before publishing any farther posts.

And it is just too hard to check if all fields exists each time, things need to be automated that is more fun 😅 Now with blogtools I know exactly what i missed :

.\blogtools.exe frontmatter .\content\posts\hello.mdx
 
file        .\content\posts\hello.mdx
valid       false
errors      2
 
errors
  tags:        missing
  readingTime: missing

So why build blogtools at all?

Why not just use a method that validates the missing metadata of a post inside my website code.

export type PostMeta = {
  title: string;
  date: string;
  tags: string[];
  excerpt: string;
  readingTime: string;
};
 
function validateFrontmatter(data: Partial<PostMeta>, filename: string) {
  const required = ['title', 'date', 'tags', 'excerpt', 'readingTime']
  const missing = required.filter(field => !data[field as keyof PostMeta])
 
  if (missing.length > 0) {
    throw new Error(
      `Invalid frontmatter in ${filename} — missing: ${missing.join(', ')}`
    )
  }
}

Technically that should work, and the broken posts won't get deployed, but i would hate finding out on production, plus it would work just for this website project.

That's why I build blogtools :

Structuring it as a package not one big file

Well simply because that would be a nightmare. Imagine a garage full of hand tools everywhere, it's unmanageable and it gets messy real quick.

So using packages is me organizing the hand tools or blog tools in my case into drawers with labels. That way when wanting to modify or upgrade a tool it would be much easier to do.

Here is what blogtools contains so far:

blogtools/
├── readingtime/
├── slugify/
└── frontmatter/

Writing unit tests

When I started, I went directly into coding the blog tools. without having an entry point main.go.
So that's where the unit test came in handy.

func TestValidate(t *testing.T) {
 
    tests := []struct {
        name           string
        input          string
        expectedValid  bool
        expectedErrors int
    }{
        {
            name: "invalid date format",
            input: `---
					title: "Test Post"
					date: "01-06-2025"
					tags: ["Go"]
					excerpt: "Some excerpt."
					readingTime: "3 min read"
					---
					Content.`,
            expectedValid:  false,
            expectedErrors: 1,
        },
        {
            name: "empty tags array",
            input: `---
					title: "Test Post"
					date: "2025-06-01"
					tags: []
					excerpt: "Some excerpt."
					readingTime: "3 min read"
					---
					Content.`,
            expectedValid:  false,
            expectedErrors: 1,
        },
        ...
 
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Validate(tt.input)
            if result.Valid != tt.expectedValid {
                t.Errorf("Valid: got %v, want %v", result.Valid, tt.expectedValid)
            }
            if len(result.Errors) != tt.expectedErrors {
                t.Errorf("Errors: got %d, want %d\nerrors: %v",
                    len(result.Errors), tt.expectedErrors, result.Errors)
            }
        })
    }
}

That's too much work you may say, and i would agree with you before. But here how I see it now:

Publishing it as a proper Go module with versioning

This was my first time publishing a Go module. I didn't know that any public GitHub repo is automatically importable as a Go module. You just tag a release and go get works.

go get github.com/ayoubnachti/blogtools@v0.1.0

That's it. No registry, no publishing step, no account to create. Go's proxy handles it automatically.

I found that simpler than I expected, and more satisfying than I expected too.

Wiring it into the blog workflow

Here is how my current blog posting workflow works:

Layer 1 — pre-commit hook blogtools runs locally before every commit. If frontmatter is invalid the commit is blocked. Fastest possible feedback.

Layer 2 — Vercel build If something slips through, the build throws and deployment is cancelled. Nothing broken goes live.


Next I want to add a linter that checks for broken links, missing alt text on images, and headings that skip levels. And eventually wire the whole thing into a pre-commit hook so nothing broken ever reaches Github.

blogtools is at v0.1.0 — just getting started.