Repository navigation
Astro site rules
Status: draft. Written before milestone 3 and settled while Honey Badger moves to Slim Killer. Rules marked to settle are open questions.
The rules an Astro site follows from its first day, so Slim Killer can be added later without rework. The usual order of work is:
- Build the client's website in Astro, following these rules.
- The client approves the website.
- Add Slim Killer: the control panel, publishing and the host setup.
Step 3 should be routine. Every rule below exists because breaking it would make step 3 a rebuild. Rule numbers here are separate from the twelve build rules.
1. One repository per client, in the client-site layout from the start.
client-repo/
site/ Astro project (package.json, astro.config.mjs, src/)
content/ everything the client edits
<collection>/ one folder per collection, one Markdown file per entry
images/ the client's photos (compressed when the site is built)
graphics/ maps, flags, logos, diagrams, SVGs (never compressed)
settings/ site-wide settings (see rule 5)
<page>/index.md a page that exists once: home, about, not-found
<collection>-list/ a list page's own text: articles-list
.github/workflows/ build-site.yml, from Slim Killer's template
cp/ (the control panel) is added in step 3. Why: moving content and changing paths
after approval is exactly the rework these rules prevent.
2. Everything the client might change lives in content/, never in .astro files.
The test: "could the client want to change this sentence or photo without a developer?"
If yes, it is content. Headings, intros, prices, team members, opening hours, the footer
text: content. Why: the control panel can only edit what is in content/.
3. Three kinds of image, three places.
-
Photos live in
content/images/. Astro compresses them to WebP when it builds the site. -
Graphics live in
content/graphics/: maps, flags, logos, diagrams and every SVG. They are copied into the site exactly as they are. Why: they are already optimised; compressing them again makes them worse and hardly smaller. -
The theme's own images (its logo, icons, textures) live in
site/src/assets/.
An image field holds a file name ("oryx.jpg"). The site finds the file in
content/images/ with import.meta.glob at build time. The control panel decides
photo or graphic when a file is uploaded: an SVG is always a graphic, and a PNG or JPEG
that WebP makes less than about 25% smaller is a graphic too. The client never chooses.
4. Every kind of content is a collection, with its own schema, in
site/src/content.config.ts, loaded from ../content/<collection>. One schema per
kind of page. A page that exists once (About, FAQ, Privacy) is its own collection with
one entry, content/<page>/index.md, never one schema shared by different pages. Why: spike S3 found that a
schema shared by six pages gives every page every other page's fields in the editor.
5. Site-wide settings are content too. The business names (businessName,
shortName), phone numbers, email address, physical address, social links, menus and
footer text live in one settings collection (content/settings/index.md), not in
constants inside components.
6. Every field carries its editor hint. .meta({ title }) with the label in the
client's words; input: 'image' on every image field; input: 'textarea' or
'markdown' on long text; description wherever a client might hesitate. Field names
follow Field names and building blocks: one name for one idea, groups instead of
prefixes (cta.heading, not ctaHeading), and every image an image() of
{ src, alt }. Why: the control panel builds its forms from these (spike S3), and
nothing else describes the fields. See the Glossary for "editor hint".
7. Required only when the page breaks without it. Everything else is .optional()
or has a .default(). Why: every required field is one the client must fill in before
she can save.
8. Bodies are plain Markdown. CommonMark plus tables. No MDX, no HTML blocks, no components inside a body. Layout variety (sections with an image beside the text, quotes, galleries) comes from front matter fields that the template renders. Why: the editor can keep plain Markdown unchanged (spike S4), and cannot edit components inside text. Obsidian-style callouts wait until the editor supports them.
9. File names are web addresses. Lowercase, hyphens, no spaces: fish-river-canyon.md
becomes /articles/fish-river-canyon/. Files starting with _ are not content
(templates, notes for the client) and are never published.
10. One front matter style. UTF-8 without a byte-order mark, Unix line endings,
double quotes, | for text over several lines. Why: the editor keeps any style it
finds (spike S4), but files made in one style keep every diff small and readable.
11. npm ci && npm run build in site/ makes the complete site in site/dist/.
Static output only: no server code, no secrets, no build-time API calls that can fail,
and no host-specific files such as .htaccess in dist/. Why: GitHub Actions builds
the site with nothing but the repository (spike S5), and the host serves only what is
fetched.
12. Versions are pinned. Node in package.json engines and .nvmrc; dependencies
by package-lock.json; Astro within the versions Slim Killer supports.
13. The site works at its domain root, with site set in astro.config.mjs to the
real address.
14. It publishes the Slim Killer way from day one. The workflow from Slim Killer's
template builds the site and publishes it to the dist branch, and the host fetches it
(see Glossary: build, fetch, release). The control panel arrives later; the
publishing path does not change when it does.
- The layout matches rule 1.
- No client-editable text or photo is inside
site/src/(rule 2). - No SVG, GIF or ICO in
content/images/; graphics are incontent/graphics/(rule 3). - Every page type has its own collection and schema (rule 4).
- Settings are in
content/settings/(rule 5). - Every field has a
title, and every image and long-text field aninput(rule 6). - Field names follow Field names and building blocks, and repeated shapes use its building blocks (rule 6).
- No MDX or HTML in bodies (rule 8).
-
npm ci && npm run buildworks on a clean checkout (rule 11).
Planned: hammer site:check, which checks these rules automatically, the way
hammer doctor checks a host.