[RFC] feat(nitro): embed observability dashboard in-process at /_workflow#2548
[RFC] feat(nitro): embed observability dashboard in-process at /_workflow#2548pranaygp wants to merge 1 commit into
Conversation
Serve the @workflow/web observability UI inside the Nitro process at a configurable route (default /_workflow) instead of spawning a separate web server and 302-redirecting to it. Enabled in dev, omitted from production builds by default (so prod bundles carry no @workflow/web import). Never mounted on Vercel deploys (use the hosted dashboard). - @workflow/web: add a framework-neutral `@workflow/web/handler` (createWorkflowWebHandler) that serves SSR + static client assets + RPC as one Web Request->Response handler under a runtime basename (asset manifest URLs + publicPath are reprefixed so the dashboard is self-contained under its mount). Add `@workflow/web/registry` for embedded-dashboard discovery; make the RPC/stream client basename-aware. - @workflow/nitro: mount the handler in-process (Nitro v2 h3 + v3 native paths), gated by a new `dashboard` option (default = dev). - @workflow/cli: `workflow web` / `inspect --web` defer to a running embedded dashboard instead of starting a redundant server; pass `--standalone` to force the standalone UI. Co-Authored-By: Claude Opus 4.8 <[email protected]>
🦋 Changeset detectedLatest commit: 4ba2a5a The changes in this PR will be included in the next version bump. This PR includes changesets to release 16 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
📊 Benchmark Results
workflow with no steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express workflow with 1 step💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Express | Nitro workflow with 10 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) workflow with 25 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Next.js (Turbopack) | Nitro workflow with 50 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) Promise.all with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) Promise.all with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Next.js (Turbopack) | Nitro Promise.all with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express Promise.race with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express Promise.race with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) Promise.race with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) workflow with 10 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express workflow with 25 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) workflow with 50 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) workflow with 10 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) workflow with 25 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) workflow with 50 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express stream pipeline with 5 transform steps (1MB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Express | Nitro 10 parallel streams (1MB each)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express fan-out fan-in 10 streams (1MB each)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
|
🧪 E2E Test Results✅ All tests passed Summary
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
✅ 📋 Other
|
Context
Using Workflow with a Nitro-based framework (Nitro v2/v3, Nuxt) used to run the local observability UI as a separate process: the dev-only
/_workflowhandler booted the entire@workflow/webExpress server on a random port and 302-redirected to it. That second server, second port, and cross-origin redirect is what this PR eliminates.Now the dashboard is served in-process, on the same origin/port, at a configurable route (default
/_workflow) — no second server, no redirect, no orphan process.This is the first half of making
@workflow/nitroan all-in-one integration. The other half — auto-starting the world on boot — is a follow-up PR that builds on #2544 (ensureWorldStarted) and is intentionally not included here.Changes
@workflow/web— new framework-neutral@workflow/web/handlerexport (createWorkflowWebHandler({ basename })) that serves SSR + static client assets + RPC as a single WebRequest→Responsehandler under a runtime mount path. The React Router build (Vite base/) is reprefixed at runtime — asset-manifest URLs +publicPath— so the dashboard is self-contained under its mount (<basename>/assets/...), never touching the host app's root namespace. Adds@workflow/web/registry(best-effort discovery) and makes the RPC/stream client basename-aware. The standalone@workflow/web/server(CLI at/) is unchanged.@workflow/nitro—addDashboardHandlernow mounts that handler in-process (Nitro v2h3.fromWebHandler+ v3 native paths) instead of redirecting. Newdashboard?: boolean | { enabled?, path? }option; default = on in dev, off in prod. When disabled nothing is registered, so production bundles carry no@workflow/webimport (zero bundle/startup cost). Excluded on Vercel deploys.@workflow/cli—workflow web/inspect --webdetect an already-running embedded dashboard (via the registry, health-checked) and open it instead of spawning a redundant server on:3456.--standaloneforces the standalone UI.Because the embedded data layer shares the host process's
process.env, it reads the same world as the running app automatically (no extra wiring).Test plan
Verified manually across all three targets + prod gating + CLI:
workbench/nitro-v3(v3 native)/_workflow/_workflow/assets/…+ servedtext/javascript, RPCsuccess:trueworkbench/nitro-v2(v2 h3)workbench/nuxt(Vite SSR)/_workflow/run/xserved by the handler (no SPA-fallback issue)/_workflowserves the app's ownindex.html; no@workflow/webin the bundle--standalonebypasses@workflow/web+@workflow/nitrounit tests pass; Biome cleanFollow-up (separate PR, not here)
Auto-start the world on server boot via a Nitro plugin (
ensureWorldStarted), gated off Vercel — depends on #2544 merging first.🤖 Generated with Claude Code