Astro Build Standards docs

Starter conformance gaps#

[conformance]Reference

Where the starter does not yet meet a rule in this document. Every entry is dated and verified. A rule marked Required with an entry here is not gated on until the entry clears.

Verified 2026-08-02.

None. Every rule marked Required is true of the starter as of the date above. New gaps go in a table here the moment one is found β€” an empty section is a claim, so it is only honest while someone keeps checking.

Cleared#

v2.11 β€” components.forms now holds. functions/api/contact.ts is a hardened reference endpoint: origin check, body-size cap, per-field length limits, CR/LF stripping on everything reaching a mail header, PII-free logging, and a stable [contact:error] prefix to alert on. Rate limiting is explicitly platform-level and marked NEEDS HUMAN in the audit β€” an in-memory counter in an ephemeral edge isolate counts nothing and looks like protection.

v2.11 β€” structure.env now holds, by correcting the rule. The starter has no required build-time keys: PUBLIC_GTAG_ID is optional by design and the endpoint keys are runtime, not build-time. The rule now carries the assertion helper to use when a project gains its first required key, and says why the starter ships none.

v2.10 β€” perf.prefetch now holds. prefetch is configured with prefetchAll and the hover strategy; the runtime is verified present in the built page entry script.

v2.10 β€” tokens.scoped-styles now holds. The two components carrying literal hex custom properties, HeroCanvas and ShinyButton, were removed rather than tokenized. Neither had a showcase entry, so neither was reachable from /components; HeroCanvas was also the sole consumer of src/lib/simplex-noise.js, which went with it. Nothing in src/ referenced either.

v2.9 β€” structure.git now holds. starterVersion ships in the starter's package.json and is inherited by every template copy. The upstream remote requirement was dropped: builds come from the GitHub template repo, which has no upstream link by design, so the rule was describing a workflow nobody uses.

v2.9 β€” not a gap, a decision. SectionMain's unconditional side rules were logged as needing an opt-out prop. They stay as they are: the shared frame is the reason the primitive exists, and a per-section prop would reintroduce the drift it prevents. A project without section borders edits SectionMain in its own repo β€” client repos own their component set (components.composition).

v2.3 β€” the launch skill no longer contradicts seo.staging. It previously blocked on a missing Disallow: / and accepted its presence as sufficient staging protection, so it would have passed an indexable staging site. It now checks for one of the four real methods, marks the two it cannot see from the repo as NEEDS HUMAN, adds the inverse production check (a blanket X-Robots-Tag: noindex in public/_headers deindexes the live site), and is explicitly barred from "fixing" the finding by adding Disallow: /.

v2.2 β€” seo.identity now holds. astro.config.mjs imports site.url from src/data/site.ts; the domain is declared in one place, verified end to end β€” a real domain reaches rel="canonical" in the built HTML. The placeholder guard that shipped alongside it was reverted in v2.4: it warns everywhere and fails nothing, and checklist.pre-launch is the gate.

v2.2 β€” structure.gate now holds. typecheck runs astro check --minimumFailingSeverity warning, and the three errors it surfaced are fixed: ambient declarations for the untyped @fontsource-variable/* packages, CodeBlock's lang prop derived from <Code /> instead of a bare string, and SliderBasicMap's items typed optional to match its own default and documented usage. Result: 0 errors, 0 warnings, 71 files. 33 hints remain (unused locals, and the deprecated z re-export in content.config.ts) β€” hints don't fail the gate; see roadmap.

v2.1 β€” the build-component command β€” which hardcoded one client's radius stance, pointed at a machine-specific path, and restated rules β€” was deleted. Its three pieces of unique content (the Astro :global() scoping trap, the hand-rolled focus-ring equivalent, and the BEM-vs-utilities naming guidance) were rescued into components.styling first.

v2 β€” three entries removed as stale after checking them against the code: Button.astro already uses interface Props extends HTMLAttributes<"button"> with the intersection deliberately rejected; the script-init flag is already uniform across every component; there are zero raw Tailwind neutral classes in components/.