Summary
Apps built with buildApp() ship the identical <title> and meta description on every generated page. The document shell resolves the title as options.title || configTitle (src/document-shell.ts:78), and the SSG path in src/build.ts never derives a per-page title from the view — there is no per-page head mechanism wired in at all:
@section('title', ...)in a view extending a body-fragment layout has no@yieldtarget in the shell model, so it is silently dropped.- The
directive exists (processHeadDirectiveinsrc/head.ts), but it is only invoked from the full-document template processor (src/process.ts), not from the app-shell/SSG build path. - If a page does author its own
<head>,stripDocumentWrapper()(src/app-shell.ts) explicitly strips<meta>,<title>, and<link>tags, preserving only<style>— so per-page titles are actively discarded, then replaced with the global config title.
Impact
SEO is effectively blocked for any stx SSG app: crawlers and link scrapers see one site-wide title/description. Real-world case: bench-review has ~30 views authoring @section('title', ...), yet every page in dist/ carries the same stx.config.ts title verbatim; per-page titles only apply via client JS after hydration, which social scrapers and the first indexing wave never execute.
Suggested fix
During buildApp() page generation, extract per-view head content and pass it into the document shell:
- Honor
by runningprocessHeadDirectivein the app-shell path and feeding the result into the shell's head composition (alongsideoptions.title). - Map
@section('title', ...)tooptions.titleas a convenience (it is the idiom users already reach for). - Alternatively (or additionally), stop stripping
<title>/<meta>instripDocumentWrapper()and hoist them into the shell head, letting page-authored head tags win over config defaults.