Overview
Can the integration be updated to work with newer versions of Astro using Qwik v1.x? Below captures the outcome of upgrading to Astro 6 and the qwik.dev/astro v1 integration, which then pulls in Qwik v2-beta. In practice that beta dependency introduces several breaking changes and ships dev-mode output rather than production code.
Result of app at Astro 6, qwik.dev/astro 1, Qwik v2-beta
Qwik v2-beta removes preloader option
- Configuring the preloader is critical for the app's Lighthouse score
- Lighthouse score takes a significant hit
Qwik v2-beta breaks Qwik component integration tests
- Qwik testing library dependency has not been updated for Qwik v2-beta. Have to install Qwik v1 as a dependency for testing. App now has 2 versions of Qwik.
Qwik v2-beta package distribution is not production code. The output is from dev mode. See "Qwik v2-beta Dev-mode output" below. Considerable configuration to Vite is required.
Qwik v2-beta Dev-mode output
Error: Code(Q30): Qwik version 2.0.0-beta.35-dev already imported while importing 2.0.0-beta.35-dev.
This can lead to issues due to duplicated shared structures.
Verify that the Qwik libraries you're using are in "resolve.noExternal[]" and in "optimizeDeps.exclude".
Stack trace points to:
node_modules/dist-dev/tsc-out/packages/qwik/src/core/index.js
Not a Duplicate npm Package
Running npm ls @qwik.dev/core confirms only one copy is installed.
npm has already deduplicated the package. Adding overrides in package.json cannot help because there is nothing to collapse.
The Real Cause: Conditional Exports Resolved Differently by Vite vs Node
The node_modules/dist-dev/tsc-out/... paths in the Q30 stack trace are not real files and not a Vite alias. They are source map annotations embedded in core.mjs.map that point back to the Qwik monorepo's internal dev-build layout. vite-node displays source-mapped paths in stack traces, which made the path appear meaningful, it isn't.
The actual trigger is @qwik.dev/core's conditional exports:
// node_modules/@qwik.dev/core/package.json (abridged)
"exports": {
".": {
"import": {
"development": "./dist/core.mjs",
"production": "./dist/core.prod.mjs",
"default": "./dist/core.prod.mjs"
}
}
}
In a Vitest run, two resolution paths are active simultaneously:
| Loader |
Export condition used |
File loaded |
| vite-node (inlined modules) |
"development" (Vite adds this) |
dist/core.mjs |
| Node native (external modules) |
"default" |
dist/core.prod.mjs |
Both core.mjs and core.prod.mjs contain independent copies of Qwik's module-level singleton. When both files are executed in the same process — one through Vite, one through Node — the singleton is initialized twice, and Q30 fires.
The isDev export in optimizer.mjs (line 974) is a code generator template, not a baked-in constant:
// This function generates the virtual @qwik.dev/core/build module at runtime:
const S = t.buildMode === "development";
return `export const isDev = ${JSON.stringify(S)};`;
It has no bearing on which file is loaded from the conditional exports.
Why package.json Cannot Fix This
package.json overrides operate at the npm installation level. They can force a specific package version to be resolved but cannot influence how Vite and Node resolve conditional exports differently at runtime.
Installing "the exact commit referenced in the source maps" (5421ed4) also cannot help: 2.0.0-beta.30-dev+5421ed4 is already the installed version. The dist-dev/tsc-out/... path in the stack trace is the Qwik monorepo's source map path embedded in core.mjs.map is metadata, not a file that needs to exist on the current machine.
Fix Applied
The fix forces all @qwik.dev/core imports through vite-node rather than Node's native loader. This ensures only one export condition ("development" to core.mjs) is ever used, eliminating the second singleton initialization.
vitest.config.qwik.ts inside the test: block:
server: {
deps: {
inline: ['@qwik.dev/core'], // force vite-node to resolve core.mjs always
},
},
Supporting guards that prevent other split-load vectors:
resolve: {
dedupe: ['@qwik.dev/core'], // ensure a single resolution across all plugins
},
ssr: {
noExternal: ['@qwik.dev/core'], // belt-and-suspenders for SSR context
},
optimizeDeps: {
exclude: ['@qwik.dev/core'], // keep out of pre-bundle cache
},
astro.config.ts (Vite section) — prevents the build from splitting the bundle:
resolve: {
dedupe: ['@qwik.dev/core'],
},
Overview
Can the integration be updated to work with newer versions of Astro using Qwik v1.x? Below captures the outcome of upgrading to Astro 6 and the qwik.dev/astro v1 integration, which then pulls in Qwik v2-beta. In practice that beta dependency introduces several breaking changes and ships dev-mode output rather than production code.
Result of app at Astro 6, qwik.dev/astro 1, Qwik v2-beta
Qwik v2-beta removes preloader option
Qwik v2-beta breaks Qwik component integration tests
Qwik v2-beta package distribution is not production code. The output is from dev mode. See "Qwik v2-beta Dev-mode output" below. Considerable configuration to Vite is required.
Qwik v2-beta Dev-mode output
Stack trace points to:
Not a Duplicate npm Package
Running
npm ls @qwik.dev/coreconfirms only one copy is installed.npm has already deduplicated the package. Adding overrides in
package.jsoncannot help because there is nothing to collapse.The Real Cause: Conditional Exports Resolved Differently by Vite vs Node
The
node_modules/dist-dev/tsc-out/...paths in the Q30 stack trace are not real files and not a Vite alias. They are source map annotations embedded incore.mjs.mapthat point back to the Qwik monorepo's internal dev-build layout.vite-nodedisplays source-mapped paths in stack traces, which made the path appear meaningful, it isn't.The actual trigger is
@qwik.dev/core's conditional exports:In a Vitest run, two resolution paths are active simultaneously:
"development"(Vite adds this)dist/core.mjs"default"dist/core.prod.mjsBoth
core.mjsandcore.prod.mjscontain independent copies of Qwik's module-level singleton. When both files are executed in the same process — one through Vite, one through Node — the singleton is initialized twice, and Q30 fires.The
isDevexport inoptimizer.mjs(line 974) is a code generator template, not a baked-in constant:It has no bearing on which file is loaded from the conditional exports.
Why package.json Cannot Fix This
package.jsonoverrides operate at the npm installation level. They can force a specific package version to be resolved but cannot influence how Vite and Node resolve conditional exports differently at runtime.Installing "the exact commit referenced in the source maps" (
5421ed4) also cannot help:2.0.0-beta.30-dev+5421ed4is already the installed version. Thedist-dev/tsc-out/...path in the stack trace is the Qwik monorepo's source map path embedded incore.mjs.mapis metadata, not a file that needs to exist on the current machine.Fix Applied
The fix forces all
@qwik.dev/coreimports throughvite-noderather than Node's native loader. This ensures only one export condition ("development"tocore.mjs) is ever used, eliminating the second singleton initialization.vitest.config.qwik.tsinside thetest:block:Supporting guards that prevent other split-load vectors:
astro.config.ts(Vite section) — prevents the build from splitting the bundle: