Skip to content

dev-worker: throw in fetch handler crashes Deno.serve #4219

@nestarz

Description

@nestarz

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:

  1. initEnvRunner() starts the worker during nitro's config hook
  2. The worker's onReady callback sends nitro:vite-env → creates ViteEnvRunner → calls this.reload() (async, not awaited)
  3. reload() calls this.runner.import(entryPath) which compiles the entire SSR module tree (takes seconds on cold start)
  4. Deno.serve is already accepting requests
  5. 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 {}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions