Skip to content

[🐞] v2: Inline qwikloader is emitted inside an open <svg> (foreign content), so the script never executes and q:container stays "paused" #8637

@46ki75

Description

@46ki75

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:

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

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:

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

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

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>.

Metadata

Metadata

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions