Skip to content

PATTERN Cited by 1 source

Virtual handler via Nitro for bundlerless frameworks

Virtual handler via Nitro for bundlerless frameworks is the pattern used to integrate a cross-framework SDK with bare-HTTP frameworks (Express, Hono, Fastify, Koa) that don't ship with a build system. The pattern uses Nitro (UnJS) as a build-system shim: esbuild bundles the SDK handlers, Nitro mounts them as "virtual handlers" injected into the user's HTTP server at runtime.

Canonical verbatim

From Vercel's 2026-04-21 WDK post: "For frameworks without a bundler like Express or Hono, we use Nitro instead. Nitro is a server toolkit that provides file-based routing, a build system, and other quality-of-life features such as virtual handlers that can be mounted to a server at runtime. This brings the same workflow capabilities to bare HTTP servers."

And in the framework-integration-taxonomy section: "These frameworks don't ship with a build system, i.e. they don't have a bundler, and just expose a bare HTTP server. This is where Nitro comes in. For these frameworks, WDK uses esbuild to bundle workflows, then Nitro mounts them as virtual handlers. At runtime, Nitro wraps your HTTP server and injects the virtual handlers, exposing the workflow endpoints so they're reachable from your HTTP server."

Why bundlerless frameworks need the shim

File-based-routing frameworks (Next.js, SvelteKit, Nuxt) have a built-in integration seam: drop a file into a magic directory, the framework auto-discovers it as an HTTP endpoint. The SDK's build-time phase outputs handler files into the right directory and the runtime is done.

Bare-HTTP frameworks have no such seam:

  • Express: app.get("/path", handler) is imperative; the SDK can't inject routes without the user wiring them manually.
  • Hono: same imperative model on top of the Web Fetch API.

The SDK needs a mechanism to get its handlers into the user's running HTTP server without requiring the user to write app.post("/workflow/start", wdk.start).

How Nitro bridges the gap

Nitro provides:

  1. File-based routing — the SDK can output handlers to a Nitro-recognised directory just like it would with a file-based-routing framework.
  2. Build system — esbuild orchestration for bundling the handlers (WDK's step-mode + workflow-mode outputs).
  3. Virtual handlers — handlers that aren't files on disk but virtual in-memory routes Nitro mounts at runtime.
  4. Server wrapping — at runtime Nitro "wraps your HTTP server and injects the virtual handlers" — the user's Express/Hono app runs as normal but with additional routes injected by Nitro's wrapper.

The net effect: the user installs WDK + its Nitro-based integration package, adds one config line, and the workflow endpoints appear — no manual app.post(...) wiring.

When to use

  • Integrating an SDK with bare-HTTP JS frameworks that have no built-in routing-as-file-system seam.
  • When manual endpoint wiring would push framework-specific knowledge into user code.
  • When you already use esbuild for the SDK's build-time bundle output and want to avoid adding a second bundler.

When NOT to use

  • When the framework has file-based routing already (use its native seam directly — don't add Nitro overhead).
  • When the SDK ships as a library only (no HTTP handlers to inject).
  • When Nitro's dependency weight / runtime cost is unacceptable for the target deployment shape.

Canonical instance

Vercel Workflow DevKit (WDK) uses this pattern for its Express + Hono integrations. Both frameworks are launch-supported (2026-04-21) via the Nitro shim; the user adds a Nitro-based WDK plugin to their server config and workflow endpoints appear under the usual .well-known/workflow/v1 path.

Seen in

Last updated · 476 distilled / 1,218 read