astwerk

Tree & content

These build a site rather than run in one, so they're real code rather than a hydrated widget.

Building the tree

ssg.Node and Build are the entire mechanism — this is what generates this site. See The Node tree.

go
root := ssg.Node{
	Title: "My Site",
	Page:  ssg.Templ(views.Home),
	Children: map[string]ssg.Node{
		"about":   {Title: "About", Page: ssg.Templ(views.About)},
		"style":   {CopyFrom: "style"},
		"scripts": {CompileFrom: "scripts"},
	},
}

if err := ssg.Build(root, ssg.BuildOptions{
	RelativeURLs: true, // so the same build works under a subpath
	BaseURL:      "/my-site",
}); err != nil {
	log.Fatal(err)
}
Why relative URLs

RelativeURLs makes one build work opened from disk, at a localhost root, and under a subpath — BaseURL only feeds Ctx.URL, for canonical tags. See Ctx & URLs.

Markdown collections

content.LoadDir plus Node.Generate turns a directory of markdown into one index page and one page per file, with no path arithmetic. See Content & collections.

go
func projects(dir string) ssg.Node {
	pages, err := content.LoadDir(dir)
	if err != nil {
		log.Fatal(err)
	}

	var tiles []templ.Component
	children := map[string]ssg.Node{}

	// Slugs, not a map range: map order would reshuffle the index every build.
	for _, slug := range content.Slugs(pages) {
		fm, _ := content.Decode[FrontMatter](pages[slug])
		body := pages[slug].HTML

		children[slug] = ssg.Node{
			Title: fm.Title,
			Page: func(c ssg.Ctx) templ.Component {
				return views.ProjectPage(c.Title, c.Path, c.Prefix, body)
			},
		}
		tiles = append(tiles, views.Tile(fm.Title, fm.Description, slug))
	}

	return ssg.Node{
		Title:    "Projects",
		Page:     func(c ssg.Ctx) templ.Component { return views.ProjectIndex(c.Title, c.Path, c.Prefix, tiles) },
		Children: children,
	}
}

// "projects": projects("content/projects"),

Locales

A locale's subtree is derived from the base tree — you list only the pages that genuinely differ, and everything else is mirrored with the prefix set. See Locales & i18n.

go
root = ssg.BuildLocales(root, []ssg.Locale{{
	Code:   "de",
	Prefix: "/de", // defaults to "/" + Code
	Override: map[string]func(ssg.Node) ssg.Node{
		"": func(n ssg.Node) ssg.Node {
			n.Title, n.Page = "Meine Seite", ssg.Templ(views_de.Home)
			return n
		},
		"about": func(n ssg.Node) ssg.Node {
			n.Title = "Über mich" // Page and Children survive untouched
			return n
		},
	},
}})

// An override is handed the node BuildLocales would otherwise have derived, so
// changing one field is one line. Everything not named — goals, links, and any
// generated collection children — appears under /de/ rendered by the same
// components, with Ctx.Prefix set to "/de".