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 main
2
3import (
4 "encoding/json"
5 "html/template"
6)
7
8// The Person node uses the same @id the blog's graph references, so the two
9// sites describe one entity rather than two people with the same name.
10
11const (
12 personID = baseURL + "/#person"
13 websiteID = baseURL + "/#website"
14)
15
16// jsonLD marshals a graph for the script tag. template.JS is safe because
17// encoding/json escapes <, > and & by default.
18func jsonLD(nodes ...map[string]any) template.JS {
19 buf, err := json.MarshalIndent(map[string]any{
20 "@context": "https://schema.org",
21 "@graph": nodes,
22 }, "", " ")
23 if err != nil {
24 return ""
25 }
26 return template.JS(buf)
27}
28
29func personNode() map[string]any {
30 return map[string]any{
31 "@type": "Person",
32 "@id": personID,
33 "name": "Isaac Bythewood",
34 "url": baseURL + "/",
35 "jobTitle": "Senior Solutions Architect",
36 "email": "mailto:" + contactMail,
37 "image": baseURL + "/static/og/card.png",
38 "worksFor": map[string]any{
39 "@type": "Organization",
40 "name": "Craftmaster Furniture",
41 },
42 "address": map[string]any{
43 "@type": "PostalAddress",
44 "addressLocality": "Elkin",
45 "addressRegion": "NC",
46 "addressCountry": "US",
47 },
48 "sameAs": []string{
49 "https://github.com/" + githubUser,
50 "https://blog.bythewood.me/",
51 },
52 }
53}
54
55func pageGraph(title, description, canonical string, crumbs []NavPage) template.JS {
56 nodes := []map[string]any{
57 personNode(),
58 {
59 "@type": "WebSite",
60 "@id": websiteID,
61 "url": baseURL + "/",
62 "name": "Isaac Bythewood",
63 "description": siteDesc,
64 "inLanguage": "en",
65 "publisher": map[string]any{"@id": personID},
66 },
67 {
68 "@type": "WebPage",
69 "@id": canonical + "#webpage",
70 "url": canonical,
71 "name": title,
72 "description": description,
73 "isPartOf": map[string]any{"@id": websiteID},
74 "about": map[string]any{"@id": personID},
75 "inLanguage": "en",
76 },
77 }
78
79 // Below the root only. On the home page the trail would be one item
80 // pointing at itself.
81 if len(crumbs) > 0 {
82 items := []map[string]any{
83 {"@type": "ListItem", "position": 1, "name": "Home", "item": baseURL + "/"},
84 }
85 for i, c := range crumbs {
86 items = append(items, map[string]any{
87 "@type": "ListItem",
88 "position": i + 2,
89 "name": c.Title,
90 "item": baseURL + c.Href,
91 })
92 }
93 nodes = append(nodes, map[string]any{
94 "@type": "BreadcrumbList",
95 "itemListElement": items,
96 })
97 }
98
99 return jsonLD(nodes...)
100}