Preliminary Checks
Reproduction
https://github.com/ericcorriel/clerk-nuxt-bug
Publishable key
pk_test_aW5maW5pdGUtZmxlYS04Ni5jbGVyay5hY2NvdW50cy5kZXYk
Description
Repro repo
https://github.com/ericorriel/clerk-nuxt-bug
Standalone minimal Nuxt 4.4.4 + @clerk/nuxt 2.2.9 project. Clone, yarn install, copy .env.example to .env and paste Clerk dev keys, yarn dev, visit http://localhost:3000/ — the bug fires immediately on first request.
The repo also includes the workaround we shipped (commented-out config block + a hand-rolled middleware in server/middleware/clerk.ts). Uncomment the workaround block in nuxt.config.ts, rm -rf .nuxt, restart, and the page renders cleanly.
Summary
When @clerk/nuxt is added to a Nuxt 4 app's modules array with default settings, every SSR-rendered page throws ReferenceError: H3Error is not defined at module-load time. The Nitro dev bundle's auto-imports virtual module references h3 helpers (H3Error, H3Event, appendCorsHeaders, and others) without emitting the corresponding import statements at the top of the bundle.
Disabling Clerk's auto-registered server middleware via clerk: { skipServerMiddleware: true } is a workaround, but importing clerkMiddleware from @clerk/nuxt/server to register the middleware manually re-triggers the bug. Only avoiding @clerk/nuxt/server entirely (and using @clerk/backend directly) sidesteps it.
Reproduced on
| Package |
Versions tested |
nuxt |
4.3.1, 4.4.4 (latest stable) |
@clerk/nuxt |
2.2.8, 2.2.9 (latest stable) |
@nuxt/nitro-server |
4.3.1, 4.4.4 |
unimport |
5.6.0, 5.7.0, 6.2.0 (latest) |
h3 |
1.15.6 |
The bug is consistent across all combinations above.
Stack trace
ReferenceError: H3Error is not defined
at /path/to/.nuxt/dev/index.mjs:7673:12
at ModuleJob.run (node:internal/modules/esm/module_job:343:25)
at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:665:26)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:117:5)
The line number varies slightly with bundle size; the symbol may also be H3Event, appendCorsHeaders, setHeaders, etc. — anything in @nuxt/nitro-server's h3 auto-imports preset that user code doesn't already import explicitly.
Diagnosis
Inspecting the generated .nuxt/dev/index.mjs, the bundle contains an auto-imports virtual module:
const _virtual__imports = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
// ... user-defined symbols ...
H3Error: H3Error, // <-- ReferenceError fires here
H3Event: H3Event,
appendCorsHeaders: appendCorsHeaders,
// ... more h3 symbols ...
}));
But the top-of-file h3 import only includes the subset of h3 helpers that user code references explicitly:
import { defineEventHandler, createError, eventHandler, /* ... ~30 more ... */ } from 'h3';
// H3Error, H3Event, appendCorsHeaders NOT in this list
So H3Error/H3Event/etc. are referenced as values in the virtual imports object but were never imported into the bundle's scope.
@nuxt/nitro-server registers these as auto-imports via:
// node_modules/@nuxt/nitro-server/dist/index.mjs (at line 229 in 4.4.4)
presets: [{
from: 'h3',
imports: ['H3Event', 'H3Error']
}, ...]
This preset works correctly when @clerk/nuxt is not in the modules array — the bundler emits the corresponding import statements and everything is fine. Adding @clerk/nuxt is the trigger.
What we tried (none of these worked)
- Pinning unimport to
5.6.0, 5.7.0, and ^6.2.0 via yarn resolutions. Same error every time.
- Explicit user import: adding
import { H3Error, H3Event } from 'h3' to a server plugin file. The bundler tree-shakes the plugin away or unimport strips the import as already-auto-registered. Either way the import statement never appears in the final bundle.
- Explicit
nitro.imports.imports config with [{ name: 'H3Error', from: 'h3' }, ...]. No effect — the bundle still misses the import.
- Stripping the h3 value-preset via a
nitro:config hook in nuxt.config.ts. This made H3Error materialize correctly, but the next preset entry (appendCorsHeaders) failed identically — the bug is preset-wide, not specific to particular symbols.
- Upgrading Nuxt from 4.3.1 to 4.4.4. Bug persists.
- Manual middleware registration in
server/middleware/clerk.ts with import { clerkMiddleware } from '@clerk/nuxt/server'. This re-triggers the bug — importing anything from @clerk/nuxt/server is enough; the auto-registration isn't the only path that breaks the bundle.
Workaround that worked
- Set
clerk: { skipServerMiddleware: true } in nuxt.config.ts.
- Hand-roll a Nitro middleware using
@clerk/backend directly:
// server/middleware/clerk.ts
import { createClerkClient } from '@clerk/backend'
let clerkSingleton: ReturnType<typeof createClerkClient> | null = null
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig(event)
const secretKey = config.clerk?.secretKey
if (!secretKey) {
event.context.auth = () => ({ isAuthenticated: false, userId: null, sessionId: null, sessionClaims: null })
return
}
if (!clerkSingleton) {
clerkSingleton = createClerkClient({
secretKey,
publishableKey: config.public?.clerk?.publishableKey
})
}
try {
const requestState = await clerkSingleton.authenticateRequest(toWebRequest(event), { acceptsToken: 'any' })
event.context.auth = () => requestState.toAuth()
if (requestState.headers) {
requestState.headers.forEach((value, key) => setResponseHeader(event, key, value))
}
} catch {
event.context.auth = () => ({ isAuthenticated: false, userId: null, sessionId: null, sessionClaims: null })
}
})
This is functionally identical to the clerkMiddleware() export from @clerk/nuxt/server for our use case (we don't use keyless mode or the handshake redirect flow). Importantly, it imports only from @clerk/backend (a transitive dep we already pull in via @clerk/nuxt) and from h3 — no @clerk/nuxt/server path is involved at the bundle level.
With this in place, the bundler emits import { ... } from 'h3' correctly with all the symbols the auto-imports virtual module references, the bundle loads, and event.context.auth() works exactly as before.
Hypothesis
Something in @clerk/nuxt's server-runtime entry or its internal exports interacts with unimport's scanner in a way that registers h3 auto-imports as "tracked" but somehow short-circuits the bundler's import-injection step. The corruption manifests in any code path that pulls @clerk/nuxt/server into the bundle — auto-registered middleware, manually-registered middleware via clerkMiddleware() import, possibly other entry points.
I haven't isolated the exact mechanism, but the workaround above sidesteps it cleanly by using @clerk/backend directly. Happy to add more diagnostics if helpful.
Environment
- macOS 26 (Apple Silicon)
- Node.js (latest LTS)
- yarn 1.22.22
- Nuxt 4.4.4 with Vite/Nitro defaults
- Other Nuxt modules in our production project:
shadcn-nuxt, @nuxtjs/tailwindcss, @nuxtjs/color-mode. Removing these does not affect the bug — the standalone repro repo above includes none of them.
Impact
For any Nuxt 4 app adopting @clerk/nuxt server-side, this is a hard blocker on default usage — every SSR page crashes. The workaround restores functionality but adds maintenance burden (the project has to track when the upstream fix lands and remove the workaround). It also subtly changes behavior on edge cases not covered by the hand-rolled middleware (keyless mode, handshake redirects).
A Nuxt-4 + Clerk migration was the trigger that surfaced this for us; we're shipping the workaround in production while we wait for an upstream fix. Happy to test patches or canaries against the repro repo.
Environment
- macOS 26 (Apple Silicon)
- Node.js (latest LTS)
- yarn 1.22.22
- Nuxt 4.4.4 with Vite/Nitro defaults
- Other Nuxt modules in our production project: `shadcn-nuxt`, `@nuxtjs/tailwindcss`, `@nuxtjs/color-mode`. Removing these does not affect the bug — the standalone repro repo above includes none of them.
Preliminary Checks
I have reviewed the documentation: https://clerk.com/docs
I have searched for existing issues: https://github.com/clerk/javascript/issues
I have not already reached out to Clerk support via email or Discord (if you have, no need to open an issue here)
This issue is not a question, general help request, or anything other than a bug report directly related to Clerk. Please ask questions in our Discord community: https://clerk.com/discord.
Reproduction
https://github.com/ericcorriel/clerk-nuxt-bug
Publishable key
pk_test_aW5maW5pdGUtZmxlYS04Ni5jbGVyay5hY2NvdW50cy5kZXYk
Description
Repro repo
https://github.com/ericorriel/clerk-nuxt-bug
Standalone minimal Nuxt 4.4.4 + @clerk/nuxt 2.2.9 project. Clone,
yarn install, copy.env.exampleto.envand paste Clerk dev keys,yarn dev, visithttp://localhost:3000/— the bug fires immediately on first request.The repo also includes the workaround we shipped (commented-out config block + a hand-rolled middleware in
server/middleware/clerk.ts). Uncomment the workaround block innuxt.config.ts,rm -rf .nuxt, restart, and the page renders cleanly.Summary
When
@clerk/nuxtis added to a Nuxt 4 app'smodulesarray with default settings, every SSR-rendered page throwsReferenceError: H3Error is not definedat module-load time. The Nitro dev bundle's auto-imports virtual module references h3 helpers (H3Error,H3Event,appendCorsHeaders, and others) without emitting the correspondingimportstatements at the top of the bundle.Disabling Clerk's auto-registered server middleware via
clerk: { skipServerMiddleware: true }is a workaround, but importingclerkMiddlewarefrom@clerk/nuxt/serverto register the middleware manually re-triggers the bug. Only avoiding@clerk/nuxt/serverentirely (and using@clerk/backenddirectly) sidesteps it.Reproduced on
nuxt4.3.1,4.4.4(latest stable)@clerk/nuxt2.2.8,2.2.9(latest stable)@nuxt/nitro-server4.3.1,4.4.4unimport5.6.0,5.7.0,6.2.0(latest)h31.15.6The bug is consistent across all combinations above.
Stack trace
The line number varies slightly with bundle size; the symbol may also be
H3Event,appendCorsHeaders,setHeaders, etc. — anything in@nuxt/nitro-server's h3 auto-imports preset that user code doesn't already import explicitly.Diagnosis
Inspecting the generated
.nuxt/dev/index.mjs, the bundle contains an auto-imports virtual module:But the top-of-file h3 import only includes the subset of h3 helpers that user code references explicitly:
So
H3Error/H3Event/etc. are referenced as values in the virtual imports object but were never imported into the bundle's scope.@nuxt/nitro-serverregisters these as auto-imports via:This preset works correctly when
@clerk/nuxtis not in the modules array — the bundler emits the correspondingimportstatements and everything is fine. Adding@clerk/nuxtis the trigger.What we tried (none of these worked)
5.6.0,5.7.0, and^6.2.0via yarnresolutions. Same error every time.import { H3Error, H3Event } from 'h3'to a server plugin file. The bundler tree-shakes the plugin away or unimport strips the import as already-auto-registered. Either way the import statement never appears in the final bundle.nitro.imports.importsconfig with[{ name: 'H3Error', from: 'h3' }, ...]. No effect — the bundle still misses the import.nitro:confighook in nuxt.config.ts. This madeH3Errormaterialize correctly, but the next preset entry (appendCorsHeaders) failed identically — the bug is preset-wide, not specific to particular symbols.server/middleware/clerk.tswithimport { clerkMiddleware } from '@clerk/nuxt/server'. This re-triggers the bug — importing anything from@clerk/nuxt/serveris enough; the auto-registration isn't the only path that breaks the bundle.Workaround that worked
clerk: { skipServerMiddleware: true }innuxt.config.ts.@clerk/backenddirectly:This is functionally identical to the
clerkMiddleware()export from@clerk/nuxt/serverfor our use case (we don't use keyless mode or the handshake redirect flow). Importantly, it imports only from@clerk/backend(a transitive dep we already pull in via@clerk/nuxt) and fromh3— no@clerk/nuxt/serverpath is involved at the bundle level.With this in place, the bundler emits
import { ... } from 'h3'correctly with all the symbols the auto-imports virtual module references, the bundle loads, andevent.context.auth()works exactly as before.Hypothesis
Something in
@clerk/nuxt's server-runtime entry or its internal exports interacts with unimport's scanner in a way that registers h3 auto-imports as "tracked" but somehow short-circuits the bundler's import-injection step. The corruption manifests in any code path that pulls@clerk/nuxt/serverinto the bundle — auto-registered middleware, manually-registered middleware viaclerkMiddleware()import, possibly other entry points.I haven't isolated the exact mechanism, but the workaround above sidesteps it cleanly by using
@clerk/backenddirectly. Happy to add more diagnostics if helpful.Environment
shadcn-nuxt,@nuxtjs/tailwindcss,@nuxtjs/color-mode. Removing these does not affect the bug — the standalone repro repo above includes none of them.Impact
For any Nuxt 4 app adopting
@clerk/nuxtserver-side, this is a hard blocker on default usage — every SSR page crashes. The workaround restores functionality but adds maintenance burden (the project has to track when the upstream fix lands and remove the workaround). It also subtly changes behavior on edge cases not covered by the hand-rolled middleware (keyless mode, handshake redirects).A Nuxt-4 + Clerk migration was the trigger that surfaced this for us; we're shipping the workaround in production while we wait for an upstream fix. Happy to test patches or canaries against the repro repo.
Environment