Bug: throw in ViteEnvRunner.fetch() crashes Deno.serve
Summary
In dev-worker.mjs:77, ViteEnvRunner.fetch() throws an error when the SSR entry hasn't finished loading:
if (!this.entry) {
throw httpError(503, `Vite environment "${this.name}" is unavailable`);
}
This throw propagates as an unhandled rejection to Deno.serve, which expects the handler to return a Response. Deno crashes with:
Terminating Deno.serve loop due to unexpected error TypeError: Cannot read properties of undefined (reading 'status')
at mapped (ext:deno_http/00_serve.ts:438:26)
Why this happens
The race condition:
initEnvRunner() starts the worker during nitro's config hook
- The worker's
onReady callback sends nitro:vite-env → creates ViteEnvRunner → calls this.reload() (async, not awaited)
reload() calls this.runner.import(entryPath) which compiles the entire SSR module tree (takes seconds on cold start)
Deno.serve is already accepting requests
- A browser request arrives before
this.entry is set → fetch() throws → Deno.serve crashes
The retry loop (lines 70-72) waits up to ~3.1s, but on cold start the SSR entry tree compilation often takes longer.
Environment
- Runtime: Deno 2.7
- Vite: 8.0.9
- Nitro: 3.0.260415-beta
- SSR config:
noExternal: true (to prevent duplicate React instances)
Fix
Line 77 should return a proper Response instead of throwing:
if (!this.entry) {
- throw httpError(503, `Vite environment "${this.name}" is unavailable`);
+ return renderError(req, httpError(503, `Vite environment "${this.name}" is unavailable`));
}
renderError is already defined in the same file and returns a proper Response object with the correct status code. This matches how entryError is handled on lines 67-68 and 73-74.
Workaround
Patching the file at config load time in vite.config.ts:
import { readFileSync, writeFileSync } from "node:fs";
const devWorkerPath = resolve(
import.meta.dirname!,
"../../node_modules/.deno/nitro@3.0.260415-beta/node_modules/nitro/dist/runtime/internal/vite/dev-worker.mjs",
);
try {
const src = readFileSync(devWorkerPath, "utf-8");
const bad = 'throw httpError(503, `Vite environment "${this.name}" is unavailable`)';
if (src.includes(bad)) {
writeFileSync(devWorkerPath, src.replace(bad, 'return renderError(req, httpError(503, `Vite environment "${this.name}" is unavailable`))'));
}
} catch {}
Bug:
throwinViteEnvRunner.fetch()crashesDeno.serveSummary
In
dev-worker.mjs:77,ViteEnvRunner.fetch()throws an error when the SSR entry hasn't finished loading:This
throwpropagates as an unhandled rejection toDeno.serve, which expects the handler to return aResponse. Deno crashes with:Why this happens
The race condition:
initEnvRunner()starts the worker during nitro'sconfighookonReadycallback sendsnitro:vite-env→ createsViteEnvRunner→ callsthis.reload()(async, not awaited)reload()callsthis.runner.import(entryPath)which compiles the entire SSR module tree (takes seconds on cold start)Deno.serveis already accepting requeststhis.entryis set →fetch()throws →Deno.servecrashesThe retry loop (lines 70-72) waits up to ~3.1s, but on cold start the SSR entry tree compilation often takes longer.
Environment
noExternal: true(to prevent duplicate React instances)Fix
Line 77 should return a proper
Responseinstead of throwing:if (!this.entry) { - throw httpError(503, `Vite environment "${this.name}" is unavailable`); + return renderError(req, httpError(503, `Vite environment "${this.name}" is unavailable`)); }renderErroris already defined in the same file and returns a properResponseobject with the correct status code. This matches howentryErroris handled on lines 67-68 and 73-74.Workaround
Patching the file at config load time in
vite.config.ts: