diff --git a/packages/host/app/routes/render.ts b/packages/host/app/routes/render.ts index b0d62af3d62..44506dddc73 100644 --- a/packages/host/app/routes/render.ts +++ b/packages/host/app/routes/render.ts @@ -167,6 +167,7 @@ export default class RenderRoute extends Route { (globalThis as any).__waitForRenderLoadStability = undefined; window.removeEventListener('boxel-render-error', this.handleRenderError); this.#detachWindowErrorListeners(); + this.#removePrerenderBaseHref(); this.lastStoreResetKey = undefined; this.renderBaseParams = undefined; this.lastRenderErrorSignature = undefined; @@ -214,6 +215,14 @@ export default class RenderRoute extends Route { let parsedOptions = parseRenderRouteOptions(options); let canonicalOptions = serializeRenderRouteOptions(parsedOptions); this.#setupTransitionHelper(id, nonce, canonicalOptions); + if (!isTesting()) { + // Without this, relative `` in a card template resolves + // against the /render/... synthetic URL and 404s. Skipped in tests: + // is document-wide, and the test harness loads libraries + // (e.g. Monaco) that resolve their worker URLs against document.baseURI. + // Real prerender headless browsers don't load Monaco. CS-11146. + this.#installPrerenderBaseHref(id); + } // Stamp the "consuming realm" — the realm that owns the card being // rendered — onto a global the store-service's federated-search // wrapper reads. The realm-server's job-scoped search cache pairs @@ -936,6 +945,10 @@ export default class RenderRoute extends Route { (globalThis as any).__renderModel = undefined; (globalThis as any).__docsInFlight = undefined; (globalThis as any).__waitForRenderLoadStability = undefined; + // Same reason as the globals above: in tests the owner can be + // destroyed without deactivate firing, which would leak the + // injected into the next route/test. + this.#removePrerenderBaseHref(); }); } @@ -1509,6 +1522,46 @@ export default class RenderRoute extends Route { return id; } + #installPrerenderBaseHref(id: string): void { + if (typeof document === 'undefined') { + return; + } + let baseHref: string; + try { + baseHref = new URL('./', this.#normalizeCardId(id)).href; + } catch { + return; + } + let head = document.head; + if (!head) { + return; + } + let existing = head.querySelector( + 'base[data-prerender-base]', + ) as HTMLBaseElement | null; + if (existing) { + if (existing.href !== baseHref) { + existing.href = baseHref; + } + return; + } + let baseEl = document.createElement('base'); + baseEl.setAttribute('data-prerender-base', ''); + baseEl.href = baseHref; + if (head.firstChild) { + head.insertBefore(baseEl, head.firstChild); + } else { + head.appendChild(baseEl); + } + } + + #removePrerenderBaseHref(): void { + if (typeof document === 'undefined') { + return; + } + document.head?.querySelector('base[data-prerender-base]')?.remove(); + } + #fallbackDepsFromIds(ids: (string | undefined)[]): string[] { // Seed dependency ids in every shape we might see in index/module rows: // original id, normalized card id, and `.json` variants. This keeps error