[🐞] v2: Inline qwikloader is emitted inside an open `<svg>` (foreign content), so the script never executes and `q:container` stays `"paused"`
bugruntimeSSR
### Which component is affected?
Qwik Runtime
### Describe the bug
In Qwik v2 dev SSR, `SSRContainer.openElement` inlines the
`<script id="qwikloader">` tag as soon as the buffered response crosses
30 KB. The check does not consider the current element-frame stack, so
when the threshold is crossed while the writer is inside an open
`<svg>` (or `<math>`) subtree, the script is written into that subtree.
The browser then parses it in HTML5 foreign-content mode, where XML
element rules apply inside `<svg>`/`<math>`. The qwikloader source
contains substrings like `for(let e=0;e<t.length;e++)`, and the
tokenizer treats `<t.length…` as the opening of an XML element. The
script element ends up in the SVG namespace and its body is truncated
at the first such pseudo-tag.
**Observable symptoms on the client:**
- `<script id="qwikloader">.namespaceURI` is
`"http://www.w3.org/2000/svg"` (not HTML) — so the browser never
executes it as a script.
- Its `textContent.length` is ~3788 chars; the original source is
~4900+ — the rest was eaten by the XML tokenizer.
- `document.documentElement.getAttribute('q:container')` stays
`"paused"` indefinitely.
- No event handlers wire up. No `useVisibleTask$` ever fires (even with
`{ strategy: "document-ready" }`). No data is fetched. The page
appears interactive (it is SSR-rendered) but is fully inert.
**Offending site:**
`node_modules/@qwik.dev/core/dist/server.mjs` (Qwik 2.0.0-beta.35) at
about **line 2042**:
```js
if (
this.$noScriptHere$ === 0 &&
this.size > 30 * 1024 &&
elementName !== "body"
) {
this.emitQwikLoaderInline();
}
```
There is no check that walks `this.currentElementFrame.parent` looking
for an `svg` / `math` ancestor before emitting the inline script.
**Suggested fix direction:** either short-circuit `emitQwikLoaderInline`
when an SVG/MathML frame is on the stack (deferring the inline emit
until the writer returns to HTML content), or constrain the inline emit
to known-safe insertion points (e.g. only when the element about to be
opened is itself in the HTML namespace).
I'd be happy to send a PR if a maintainer can confirm the approach
(walking the frame chain seems lowest-risk).
### Reproduction
https://github.com/46ki75/qwik-v2-svg-qwikloader-bug
### Steps to reproduce
```bash
pnpm install
pnpm dev
# open http://localhost:5173/
```
The `/` page renders a heading that says:
> WAITING — if this text is still here a few seconds after the page
> loads, qwikloader DID NOT execute (BUG REPRODUCED).
That heading is updated to **"RESUMED OK"** by a
`useVisibleTask$(..., { strategy: "document-ready" })`. With the bug
present, that task never fires and the heading stays on "WAITING".
### Direct evidence in DevTools
After the page loads, in the console:
```js
const s = document.getElementById("qwikloader");
({
namespaceURI: s.namespaceURI, // → "http://www.w3.org/2000/svg" (BUG)
parentTag: s.parentNode.nodeName, // → "svg"
scriptTextLen: s.textContent.length, // → 3788 (real source is ~4900 chars)
});
document.documentElement.getAttribute("q:container");
// → "paused"
```
### Byte-position scan of the SSR response
```shell
total bytes : 321735
<svg> opens at : [2094, 313291] ← second is Qwik's preloader icon
</svg> closes at : [287612, 313794]
qwikloader open at : 30994 ← inside the first <svg>! depth = 1
```
### System Info
```shell
System:
OS: Linux 6.6 Ubuntu 24.04.4 LTS (Noble Numbat) (WSL2)
CPU: (16) x64 Intel(R) Core(TM) Ultra 7 255H
Memory: 3.76 GB / 15.31 GB
Shell: 5.2.21 - /bin/bash
Binaries:
Node: 24.15.0
npm: 11.12.1
pnpm: 10.33.0
Browsers:
Chrome: 148.0.7778.167
npmPackages:
@qwik.dev/core: 2.0.0-beta.35 => 2.0.0-beta.35
@qwik.dev/router: 2.0.0-beta.35 => 2.0.0-beta.35
typescript: 5.8 => 5.8.3
undici: * => 8.3.0
vite: 7.3.2 => 7.3.2
```
### Additional Information
This is not a contrived case — it bit a real project where a text input
component had a trailing trash-can SVG icon. Whenever the surrounding
page content was large enough that the 30 KB threshold landed
mid-icon, the entire page silently stopped resuming: no console error,
no network error, just `q:container="paused"` forever and every
`useVisibleTask$` / `routeLoader$`-driven fetch never running.
It also reproduces for `<math>` (any HTML5 foreign-content subtree),
not just `<svg>`.
关闭于 2026-05-19 1 条评论