orchard
mirrorEvery site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.
blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics
1package web
2
3import (
4 "bytes"
5 "fmt"
6 "html/template"
7 "io/fs"
8 "net/http"
9)
10
11// Renderer parses a base layout plus one template per page, and renders into a
12// buffer before writing. Buffering is what makes a mid-render template error
13// recoverable: writing straight to the ResponseWriter would send the 200 and
14// half a page before anything failed.
15type Renderer struct {
16 pages map[string]*template.Template
17}
18
19// NewRenderer parses every page template against the given base layouts.
20// Layouts go into each page's own set, so a page can both {{define}} blocks and
21// {{template}} the shared chrome.
22func NewRenderer(files fs.FS, funcs template.FuncMap, layouts []string, pages []string) (*Renderer, error) {
23 r := &Renderer{pages: make(map[string]*template.Template, len(pages))}
24
25 for _, page := range pages {
26 patterns := append(append([]string{}, layouts...), page)
27 t, err := template.New("base.html").Funcs(funcs).ParseFS(files, patterns...)
28 if err != nil {
29 return nil, fmt.Errorf("parse %s: %w", page, err)
30 }
31 r.pages[page] = t
32 }
33
34 return r, nil
35}
36
37// Render writes a page, or a 500 rather than a half-written body.
38func (r *Renderer) Render(w http.ResponseWriter, status int, page string, data any) {
39 t, ok := r.pages[page]
40 if !ok {
41 http.Error(w, "internal server error", http.StatusInternalServerError)
42 panic("render: no such template " + page)
43 }
44
45 var buf bytes.Buffer
46 if err := t.Execute(&buf, data); err != nil {
47 http.Error(w, "internal server error", http.StatusInternalServerError)
48 panic(fmt.Sprintf("render %s: %v", page, err))
49 }
50
51 w.Header().Set("Content-Type", "text/html; charset=utf-8")
52 w.WriteHeader(status)
53 _, _ = buf.WriteTo(w)
54}