Four faults in the one build step that decides what a crawler receives.
Closes#233 and #232.
1. Every page preloaded /logo.png. The hero hint was keyed on the first
`loading="eager"` image, and that is the header logo, on every page. So the
image each page actually paints first was never preloaded, and React's own
preload for it was being discarded as a duplicate. It now keys on the image
React marked with a high fetch priority, matched case-insensitively, because
React writes the attribute camelCase in HTML and a case-sensitive match would
have quietly removed every preload instead.
2. The hoist moved ANY title, meta or link out of the body into <head>. An
inline <svg><title> is a picture's label, and microdata rides in
<meta itemprop>: both would have become page-level head tags the moment the
long-form copy carried an icon with a title. SVG blocks are now parked before
the hoist, and itemprop tags stay where they are.
3. `page.replace('</head>', body)` interprets `$&`, `$'` and `$$` INSIDE the
replacement, and the replacement is page copy. React escapes & into &, so
any `$` immediately before an escaped character injected markup. Copy carries
no `$` today; the next page with a price would have. Replacement is now a
split and join, and each marker must appear exactly once.
4. A render error named no route, and a Suspense fallback shipped silently as
an empty page. Both now fail the build and say which route.
Also refuses to run over its own output: dist/index.html is both the template
and the home page, so a second run without a rebuild gave every page two
canonicals.
Proven by mutation, each restored afterwards: a probe <svg><title> in the footer
stays in the body on all 19 pages and no page gains a second title; a Suspense
boundary fails the build naming the route; a second prerender run refuses; copy
reading "Save $10 & more" reaches the page literally with one root div; and
every page now preloads its own hero, with none preloading the logo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>