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
- One source format: Neorg
- One binary deploy
- Folder-based projects, not flat files
- 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:
| URL | Handler |
|---|---|
| /projects | index |
| 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.