Summary
injectSignalsRuntime inserts the runtime <script> at the offset of the first <script> anywhere in the template, with no check that the offset is after the doctype. When a page uses a full-document layout whose <head> contains no script of its own, that offset resolves ahead of <!DOCTYPE html>, so the runtime — and the store block that anchors to it — get prepended above the doctype.
A start tag before the doctype puts the document in quirks mode.
The code
packages/stx/src/runtime-injection.ts, all three build modes:
const runtimeScript = '<script data-stx-runtime src="/_stx/runtime.js"></script>'
const firstScriptInDoc = template.indexOf('<script')
if (firstScriptInDoc !== -1)
return template.slice(0, firstScriptInDoc) + runtimeScript + '\n' + template.slice(firstScriptInDoc)
if (template.includes('</head>')) { /* correct placement */ }
return runtimeScript + '\n' + templateThe </head> branch below it is right. The problem is that the indexOf('<script') branch wins first and never asks where that script is.
process.ts then anchors the store block to the runtime tag (findScriptBlockByAttribute(result, 'data-stx-runtime')), so the stores follow it above the doctype too — in our case another 54 lines.
Reproduce
Requires the combination of:
- a layout that is a full document (
<!DOCTYPE html>…</html>) whose<head>contains no<script>, and - a page using it that has a
<script client>block
Then:
$ curl -s http://localhost:3100/pricing | head -3
<!-- stx-layout: marketing -->
<script data-stx-runtime src="/_stx/runtime.js"></script>
<script data-stx-stores>;(function(){
$ curl -s http://localhost:3100/pricing | grep -n DOCTYPE | head -1
57:<!DOCTYPE html>In Chrome over CDP:
/pricing -> document.compatMode = "BackCompat" document.doctype = null
/docs -> document.compatMode = "CSS1Compat" (same layout, no client script)
/login -> document.compatMode = "CSS1Compat" (client script, but its layout's head has a script)Why this is worth fixing rather than documenting
It is invisible to every check short of compatMode. The page:
- returns 200
- renders correctly in a full-page screenshot
- logs zero console errors
- passes an SPA probe
The only symptom is that one page on the site silently uses the quirks-mode box model, line-height and table cell rules while every other page uses standards mode. That is the kind of difference that gets chased as a CSS bug for hours.
Note the two conditions are individually common and the pair is not exotic — a marketing layout that deliberately keeps script out of <head>, plus one page in that section that needs interactivity, is an ordinary shape. It just needs both at once, which is why it has not surfaced before.
Suggested fix
Anchor the insert after the doctype. Roughly, in preference order: insert after <head> / before </head> when the template is a full document, and only fall back to the first-script offset for a fragment. Something like:
const headOpen = template.search(/<head\b[^>]*>/i)
if (headOpen !== -1) {
const insertAt = headOpen + template.slice(headOpen).match(/<head\b[^>]*>/i)[0].length
return template.slice(0, insertAt) + '\n' + runtimeScript + template.slice(insertAt)
}
// fragment: no head, keep the existing first-script behaviourA regression test worth pinning: assert the emitted document's first non-comment token is <!DOCTYPE html> for a page with a <script client> on a full-document layout whose head has no script. Same family as the <html>-in-a-comment and <script-in-a-comment traps — a detector reasoning about raw offsets rather than document structure.
Workaround
The injector short-circuits on if (template.includes('data-stx-runtime')) return template, so the page can declare the tag itself and keep it in <head>:
@push('styles')
<script data-stx-runtime src="/_stx/runtime.js"></script>
@endpushPushing it from the page rather than adding it to the layout matters when the layout is shared — otherwise every page in that section downloads the signals runtime to serve the one that needs it.
Provenance
Not a recent regression. The firstScriptInDoc rule dates to at least 266220cc7e (2026-03-27), last touched in 853bebdb6d (2026-07-31). Found in bughq on stx 0.2.170 while moving a page with a <script client> onto a shared marketing layout for the first time — the app had simply never produced the triggering combination before.