diff --git a/docs/architecture/README.md b/docs/architecture/README.md
index 7b83bac..a25ddf5 100644
--- a/docs/architecture/README.md
+++ b/docs/architecture/README.md
@@ -65,13 +65,25 @@ names its route:
- a template that already carries a canonical, which means the script is being
run over its own output, since `dist/index.html` is both template and home page
-Two rules about what it moves. Metadata React leaves inline is hoisted into
-`
`, **except** inside an inline `` (an SVG `` is a picture's
-label, not the page's) and except ` ` microdata, which belongs
-beside the thing it describes. And the hero preload is keyed on the image React
-marked with a high fetch priority: keying it on `loading="eager"` matched the
-header logo, so for months every page preloaded the logo and no page preloaded
-its own hero.
+Rules about what it moves, each bought with a defect:
+
+- Metadata React leaves inline is hoisted into ``, **except** inside an
+ inline `` (an SVG `` labels a picture, not the page) and except
+ ` ` microdata, which belongs beside the thing it describes.
+- **JSON-LD is never hoisted.** React hoists only async scripts with a `src`, so
+ on the client the `ld+json` script stays in the body where its component
+ renders it. Moving it to `` made the prerendered DOM disagree with the
+ client's first render, and React responded by discarding the whole prerendered
+ page and re-rendering it, on every page, for months. Structured data is valid
+ anywhere in the document.
+- The hero preload is keyed on the image React marked with a high fetch
+ priority. Keying it on `loading="eager"` matched the header logo, so every
+ page preloaded the logo and no page preloaded its own hero.
+
+**Hydration is real again, and it is measurable.** Anything rendered only on the
+client, such as sonner's ``, mounts *after* hydration; rendering it in
+the first client pass puts a node in the tree that the prerendered HTML never
+had, which is a mismatch, and a mismatch costs the entire prerendered page.
### The three boundaries worth knowing about
diff --git a/scripts/prerender.js b/scripts/prerender.js
index fc18b6f..caac327 100644
--- a/scripts/prerender.js
+++ b/scripts/prerender.js
@@ -56,8 +56,14 @@ const TEMPLATE_TAGS_TO_STRIP = [
// in the browser and in its streaming renderer, but renderToString leaves
// them inline, so the prerenderer performs the same hoist. Leaving them in
// would put every title, canonical, and og: tag somewhere crawlers ignore.
-const HOISTABLE_TAGS =
- /]*>[\s\S]*?<\/title>| ]*?\/?>| ]*?\/?>|