How I rebuilt the blog engine

The old version was a pile of Markdown files glued together with a templating helper. It worked, but it was hard to extend with project devlogs, tags, and search.

Goals

  1. One source format: Neorg
  2. One binary deploy
  3. Folder-based projects, not flat files
  4. Search that actually understands content types

Loading pipeline

The loader walks `contentprojects<slug>/` and treats `index.norg` as the project entry point. Every other `.norg` file becomes a subpost.

func LoadProjects(dir string) ([]Project, error) {
    folders, err := os.ReadDir(dir)
    if err != nil {
        if os.IsNotExist(err) {
            return []Project{}, nil
        }
        return nil, fmt.Errorf("read projects dir: %w", err)
    }
    // ... walk folders, load index.norg, then siblings
}

Routing trick

Go's `nethttp` ServeMux picks the longest matching pattern. Registering both `projects` (exact) and `projects` (prefix) lets the same prefix family serve three URL shapes:

URLHandler
/projectsindex
projects{slug}project detail
projects{slug}/{post}subpost detail

Inline formatting sanity check

Just to prove the parser handles bold, italic, underline, spoiler and inline math E = mc^2 in the wild.

Lesson learned: parse once at startup, query in-memory at request time. No DB, no cache invalidation, no surprises.

The next devlog covers the bento grid layout.