Repository navigation
fix: make renderSSR components interactive after resume - #1
Merged
Merged
Conversation
renderSSR-rendered components never become interactive: the serialized QRLs point at segment URLs the client environment cannot serve, so click handlers silently fail to load. Red on purpose; fix follows.
Serialized dev QRLs point at path-derived segment URLs that qwikVite can only serve once the segment's parent module is transformed in the client environment of the vite server actually serving the browser — which is ctx.project.browser.vite, not ctx.project.vite. Interactions only worked when an unrelated CSR test happened to import the same component first. - warm the browser server's client module graph (test file, component module, and their local imports) before rendering, instead of building a manifest mapping from user segments: a mapping entry makes the serializer emit prod-style hash-only symbol names that dev segment modules cannot satisfy - render local components from the test file itself instead of a temp copy: segment hashes are path-salted, so temp-file QRLs could never resolve against the client-transformed test module; the transform now serves registered test files cleaned in non-client environments - eagerly resume injected containers in renderServerHTML after clearing core's processVNodeData once-per-page flag (qVNodeData on older cores, qVNodeDataProcessed on newer), so second and later containers on the same page get their vnode refs attached Verified against core 2.0.0-beta.24 (this repo) and 2.0.0-beta.37 + vite-plus (qwik-lib-template).
|
commit: |
Regression test for the multi-container resume path: a second renderSSR resume in the same page must not break the first. Passes on core 2.0.0-beta.24 (this repo) and 2.0.0-beta.37 + vite-plus (qwik-lib-template).
maiieul
force-pushed
the
fix/ssr-resume-interactivity
branch
2 times, most recently
from
July 8, 2026 07:42
89c1843 to
4a8f5fc
Compare
- replace the internal vnode-data flag deletion with the container's qDestroy() teardown (added in core beta.37) - rework renderHook as componentQrl(inlinedQrl) with a noSerialize runner prop: beta.37's dev build eagerly rejects raw-function QRL captures (Q3) - update render snapshot for beta.37's dev-mode q-hmr attribute - drop the two-live-containers test until core #8799 ships in a release
…#8799 Core dev dep points at pkg.pr.new @e70b299 (per-container processVNodeData) until the fix ships in a released beta.
cleanTestModuleForSSR and its helpers lived in ssr-plugin.ts on main; keep them there rather than relocating to utils, to keep the diff scoped.
Keep isBrowserOnlySource/referencesStrippedId/isVariableDeclaration where main had them so the diff shows them unchanged, not moved.
Passing the hook as a prop already keeps it out of the optimizer-serialized closure; CSR renders never serialize props, so noSerialize was unneeded.
…ernal They're already typed there, so drop the `as unknown` cast and the now-redundant getDomContainer guard.
CSR render() containers have no per-page vnode-data to reset. Calling qDestroy on them strips q:container before teardown blur handlers resolve, throwing Code(Q24). Scope qDestroy to renderSSR containers; add a regression test.
Single place to query [q:container] (destroyContainer + resumeQwikContainer), and explain why renderHook uses componentQrl(inlinedQrl) over component$.
Explain that dev has no manifest (core derives segment URLs from the parent module), that the manifest only overrides handler symbols, and that warming is intrinsic — SSR runs in the node server, QRLs resolve in the browser server.
Bump core to the #8816 build (QwikDev/qwik), whose dev load() bootstraps a QRL segment's parent from the segment URL when it's missing from the requesting server's graph. That removes the need to pre-crawl the browser module graph before each SSR render. The build also includes #8799, so the two-container test still runs.
maiieul
force-pushed
the
fix/ssr-resume-interactivity
branch
from
July 9, 2026 15:13
172ea56 to
cd66599
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is it?
The problem
Components rendered with
renderSSR()resumed but were never interactive — click handlers silently failed to load. Two stacked causes:renderSSR()on the same page left the container un-resumable.The first cut also reset that per-page state by hand-deleting core-internal flags (
qVNodeData/qVNodeDataProcessed) — fragile and drifting with core (it already missedqVNodeDataStarted). The correct reset is core's own teardown,container.qDestroy(), added in core beta.30. The branch was pinned to beta.24, which predates it, so this PR bumps core off beta.24.What the core bump revealed
Moving beta.24 → beta.37 surfaced two failures unrelated to the fix:
renderHookthrewCode(Q3)("captured variable in the closure can not be serialized"). The check isn't new: beta.24 accidentally resolvedcore.prod.mjsin dev (qwikVite zeroed Vite 6'sdevelopmentexport condition), so dev-only diagnostics never ran under Vitest. beta.28 restored correct dev resolution and beta.29 made the eager QRL-capture check unconditional in dev — so the bump only surfaced a long-intended check that beta.24 had been hiding.rendersnapshot changed — beta.37 adds a dev-modeq-d:q-hmrattribute to rendered elements.Core-side fixes this rides on
Dev SSR here runs in the node vite server, but the serialized QRL segment URLs are fetched by the browser's separate vite server, which never imported the component modules. An earlier revision worked around this by pre-crawling the browser module graph before every render. Two core fixes replace / support that:
load()now bootstraps a QRL segment's parent from the segment URL when it's missing from the requesting server's graph, so the pre-crawl workaround is gone.processVNodeData, needed for two live SSR containers on one page.Both are unreleased, so the core dev dependency points at a pkg.pr.new build that contains both, until they ship in a beta.
What this PR does
qDestroy()— replace the internal-flag deletion withcontainer.qDestroy()on cleanup, scoped to SSR-resumed containers (calling it on CSR containers stripsq:containerbefore teardown blur handlers resolve →Code(Q24)).>=2.0.0-beta.37).renderHook— build the probe component withcomponentQrl(inlinedQrl(...))and pass the hook in as a prop. This avoidscomponent$'s optimizer-only$()(the published dist is never optimizer-transformed) and keeps the hook out of the closure the optimizer serializes — so noQ3, and nonoSerialize.componentQrl/getDomContainerare imported, typed, from@qwik.dev/core/internal.rendersnapshot for the newq-hmrattribute.