Skip to content

Conversation

@TooTallNate
Copy link
Member

@TooTallNate TooTallNate commented Feb 11, 2026

Summary

  • Replace Next.js App Router with React Router v7.13.0 framework mode (Vite-based), eliminating the large next dependency from the web, CLI, and workflow metapackages
  • Serve the web UI in-process from the CLI via Express instead of spawning next start as a child process
  • Switch RPC transport from JSON to CBOR to preserve binary data types across the wire
  • Replace nuqs URL state management with React Router's useSearchParams
  • Replace Next.js server actions with an RPC resource route (/api/rpc) and a thin CBOR-based client

Motivation

The next package is ~300MB installed and was the single largest dependency in the monorepo. It also required spawning a separate child process from the CLI to run the o11y web server, adding complexity around process lifecycle management, port readiness polling, and environment variable forwarding.

With React Router framework mode, the web package builds to a standard Express-compatible server bundle that the CLI can import and serve directly in its own process.

What changed

Framework swap (@workflow/web):

  • next.config.ts / postcss.config.mjsreact-router.config.ts / vite.config.ts
  • src/ directory → app/ directory (React Router convention)
  • src/app/layout.tsx + layout-client.tsxapp/root.tsx
  • src/app/page.tsxapp/routes/home.tsx
  • src/app/run/[runId]/page.tsxapp/routes/run-detail.tsx
  • Path alias @/~/
  • Removed all 'use client' / 'use server' directives

Data transport:

  • Server actions → RPC resource route at /api/rpc with CBOR encoding
  • CBOR preserves Uint8Array and other binary types natively (no base64 overhead)
  • Stream reading → dedicated /api/stream/:streamId resource route

URL state:

  • nuqs (useQueryState) → useSearchParams from react-router

Fonts:

  • next/font/google → Geist .woff2 files referenced directly from node_modules/geist via @font-face in CSS

CLI integration (@workflow/cli):

  • import('@workflow/web/server').then(m => m.startServer(port))
  • No child process, no readiness polling, no cleanup handlers

Radix UI compatibility:

  • onSubmit preventDefault on AlertDialogContent and SheetContent to prevent Radix's internal <form method="dialog"> from triggering React Router route actions
  • Catch-all action on root route for any stray POSTs

Dependencies removed

  • next, swr, nuqs, @tailwindcss/postcss

Dependencies added

  • react-router / @react-router/dev / @react-router/node / @react-router/express (all 7.13.0)
  • express, vite, @tailwindcss/vite, cbor-x, isbot, cross-env
  • geist (devDep)

@changeset-bot
Copy link

changeset-bot bot commented Feb 11, 2026

🦋 Changeset detected

Latest commit: ca37040

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 15 packages
Name Type
@workflow/web Patch
@workflow/cli Patch
workflow Patch
@workflow/world-testing Patch
@workflow/core Patch
@workflow/builders Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/web-shared Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt Patch

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

@github-actions
Copy link
Contributor

github-actions bot commented Feb 11, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 490 0 38 528
✅ 💻 Local Development 418 0 62 480
✅ 📦 Local Production 418 0 62 480
✅ 🐘 Local Postgres 418 0 62 480
✅ 🪟 Windows 45 0 3 48
❌ 🌍 Community Worlds 103 41 9 153
✅ 📋 Other 123 0 21 144
Total 2015 41 257 2313

❌ Failed Tests

🌍 Community Worlds (41 failed)

turso (41 failed):

  • addTenWorkflow
  • addTenWorkflow
  • should work with react rendering in step
  • promiseAllWorkflow
  • promiseRaceWorkflow
  • promiseAnyWorkflow
  • hookWorkflow
  • webhookWorkflow
  • sleepingWorkflow
  • nullByteWorkflow
  • workflowAndStepMetadataWorkflow
  • fetchWorkflow
  • promiseRaceStressTestWorkflow
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • hookCleanupTestWorkflow - hook token reuse after workflow completion
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars)
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument
  • closureVariableWorkflow - nested step functions with closure variables
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step
  • health check (queue-based) - workflow and step endpoints respond to health check messages
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly
  • Calculator.calculate - static workflow method using static step methods from another class
  • AllInOneService.processNumber - static workflow method using sibling static step methods
  • ChainableService.processWithThis - static step methods using this to reference the class
  • thisSerializationWorkflow - step function invoked with .call() and .apply()
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE
  • instanceMethodStepWorkflow - instance methods with "use step" directive
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context
  • stepFunctionAsStartArgWorkflow - step function reference passed as start() argument
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 44 0 4
✅ example 44 0 4
✅ express 44 0 4
✅ fastify 44 0 4
✅ hono 44 0 4
✅ nextjs-turbopack 47 0 1
✅ nextjs-webpack 47 0 1
✅ nitro 44 0 4
✅ nuxt 44 0 4
✅ sveltekit 44 0 4
✅ vite 44 0 4
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 41 0 7
✅ express-stable 41 0 7
✅ fastify-stable 41 0 7
✅ hono-stable 41 0 7
✅ nextjs-turbopack-stable 45 0 3
✅ nextjs-webpack-stable 45 0 3
✅ nitro-stable 41 0 7
✅ nuxt-stable 41 0 7
✅ sveltekit-stable 41 0 7
✅ vite-stable 41 0 7
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 41 0 7
✅ express-stable 41 0 7
✅ fastify-stable 41 0 7
✅ hono-stable 41 0 7
✅ nextjs-turbopack-stable 45 0 3
✅ nextjs-webpack-stable 45 0 3
✅ nitro-stable 41 0 7
✅ nuxt-stable 41 0 7
✅ sveltekit-stable 41 0 7
✅ vite-stable 41 0 7
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 41 0 7
✅ express-stable 41 0 7
✅ fastify-stable 41 0 7
✅ hono-stable 41 0 7
✅ nextjs-turbopack-stable 45 0 3
✅ nextjs-webpack-stable 45 0 3
✅ nitro-stable 41 0 7
✅ nuxt-stable 41 0 7
✅ sveltekit-stable 41 0 7
✅ vite-stable 41 0 7
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 45 0 3
❌ 🌍 Community Worlds
App Passed Failed Skipped
✅ mongodb-dev 3 0 0
✅ mongodb 45 0 3
✅ redis-dev 3 0 0
✅ redis 45 0 3
✅ turso-dev 3 0 0
❌ turso 4 41 3
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 41 0 7
✅ e2e-local-postgres-nest-stable 41 0 7
✅ e2e-local-prod-nest-stable 41 0 7

📋 View full workflow run

@github-actions
Copy link
Contributor

github-actions bot commented Feb 11, 2026

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 0.033s (-1.2%) 1.005s (~) 0.972s 10 1.00x
💻 Local Next.js (Turbopack) 0.033s (-18.8% 🟢) 1.005s (~) 0.972s 10 1.01x
💻 Local Express 0.034s (+33.6% 🔺) 1.005s (~) 0.971s 10 1.02x
🌐 Redis Next.js (Turbopack) 0.040s (-13.9% 🟢) 1.005s (~) 0.965s 10 1.22x
🌐 MongoDB Next.js (Turbopack) 0.075s (-21.9% 🟢) 1.007s (~) 0.932s 10 2.28x
🐘 Postgres Nitro 0.154s (+16.9% 🔺) 1.009s (~) 0.856s 10 4.65x
🐘 Postgres Express 0.171s (+73.9% 🔺) 1.010s (~) 0.838s 10 5.19x
🐘 Postgres Next.js (Turbopack) 0.363s (-20.2% 🟢) 1.010s (~) 0.647s 10 11.00x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 0.680s (-25.4% 🟢) 1.895s (-15.3% 🟢) 1.215s 10 1.00x
▲ Vercel Express 0.687s (-21.8% 🟢) 2.160s (-0.8%) 1.473s 10 1.01x
▲ Vercel Nitro 0.739s (-3.0%) 1.904s (-15.9% 🟢) 1.165s 10 1.09x

🔍 Observability: Next.js (Turbopack) | Express | Nitro

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 1.079s (-2.2%) 2.006s (~) 0.926s 10 1.00x
🌐 Redis Next.js (Turbopack) 1.086s (-1.4%) 2.006s (~) 0.920s 10 1.01x
💻 Local Nitro 1.106s (-0.5%) 2.006s (~) 0.900s 10 1.02x
💻 Local Express 1.113s (+3.6%) 2.006s (~) 0.893s 10 1.03x
🌐 MongoDB Next.js (Turbopack) 1.296s (-2.0%) 2.008s (~) 0.712s 10 1.20x
🐘 Postgres Next.js (Turbopack) 1.569s (-14.6% 🟢) 2.012s (~) 0.442s 10 1.45x
🐘 Postgres Nitro 2.247s (-8.1% 🟢) 3.014s (~) 0.767s 10 2.08x
🐘 Postgres Express 2.293s (-7.3% 🟢) 3.013s (~) 0.720s 10 2.12x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.571s (-9.2% 🟢) 3.630s (-7.2% 🟢) 1.060s 10 1.00x
▲ Vercel Express 2.638s (-7.0% 🟢) 3.676s (-13.1% 🟢) 1.037s 10 1.03x
▲ Vercel Next.js (Turbopack) 3.047s (+4.6%) 3.962s (-1.2%) 0.915s 10 1.19x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 10.545s (-1.7%) 11.022s (~) 0.478s 3 1.00x
💻 Local Next.js (Turbopack) 10.571s (-1.7%) 11.022s (~) 0.451s 3 1.00x
💻 Local Nitro 10.823s (-0.7%) 11.023s (~) 0.200s 3 1.03x
💻 Local Express 10.888s (+3.1%) 11.022s (~) 0.134s 3 1.03x
🌐 MongoDB Next.js (Turbopack) 12.210s (-0.6%) 13.019s (~) 0.809s 3 1.16x
🐘 Postgres Next.js (Turbopack) 15.140s (+0.7%) 16.046s (+3.2%) 0.906s 2 1.44x
🐘 Postgres Express 20.439s (~) 21.056s (~) 0.617s 2 1.94x
🐘 Postgres Nitro 20.456s (~) 21.057s (~) 0.601s 2 1.94x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 19.085s (-13.4% 🟢) 19.661s (-14.7% 🟢) 0.576s 2 1.00x
▲ Vercel Express 19.129s (-10.4% 🟢) 19.752s (-14.5% 🟢) 0.623s 2 1.00x
▲ Vercel Next.js (Turbopack) 19.715s (-7.8% 🟢) 20.770s (-7.6% 🟢) 1.055s 2 1.03x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 26.546s (-1.4%) 27.052s (~) 0.506s 3 1.00x
💻 Local Next.js (Turbopack) 26.784s (-1.8%) 27.048s (-3.6%) 0.264s 3 1.01x
💻 Local Nitro 27.476s (~) 28.054s (~) 0.578s 3 1.04x
💻 Local Express 27.634s (+3.1%) 28.055s (+3.7%) 0.420s 3 1.04x
🌐 MongoDB Next.js (Turbopack) 30.377s (-0.7%) 31.039s (~) 0.662s 2 1.14x
🐘 Postgres Next.js (Turbopack) 38.289s (+3.7%) 38.595s (+2.7%) 0.306s 2 1.44x
🐘 Postgres Express 50.228s (~) 51.124s (+1.0%) 0.896s 2 1.89x
🐘 Postgres Nitro 50.237s (~) 51.131s (~) 0.894s 2 1.89x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 48.643s (-10.8% 🟢) 49.170s (-12.1% 🟢) 0.527s 2 1.00x
▲ Vercel Next.js (Turbopack) 49.444s (-10.9% 🟢) 50.804s (-10.9% 🟢) 1.360s 2 1.02x
▲ Vercel Express 50.353s (-7.5% 🟢) 51.767s (-8.7% 🟢) 1.413s 2 1.04x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 53.673s (-1.3%) 54.095s (-1.8%) 0.422s 2 1.00x
💻 Local Next.js (Turbopack) 55.636s (-2.0%) 56.097s (-1.8%) 0.460s 2 1.04x
💻 Local Nitro 57.255s (~) 58.104s (~) 0.848s 2 1.07x
💻 Local Express 57.466s (+3.1%) 58.108s (+3.6%) 0.642s 2 1.07x
🌐 MongoDB Next.js (Turbopack) 60.703s (-0.5%) 61.050s (~) 0.347s 2 1.13x
🐘 Postgres Next.js (Turbopack) 73.963s (+13.1% 🔺) 74.671s (+13.7% 🔺) 0.709s 2 1.38x
🐘 Postgres Nitro 98.491s (-1.9%) 99.234s (-2.0%) 0.743s 1 1.84x
🐘 Postgres Express 100.472s (~) 101.216s (~) 0.744s 1 1.87x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 96.892s (-16.3% 🟢) 97.346s (-16.6% 🟢) 0.454s 1 1.00x
▲ Vercel Next.js (Turbopack) 98.905s (-10.5% 🟢) 99.899s (-10.8% 🟢) 0.994s 1 1.02x
▲ Vercel Express 107.657s (-1.2%) 108.289s (-1.9%) 0.632s 1 1.11x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 1.185s (-6.5% 🟢) 2.006s (~) 0.821s 15 1.00x
💻 Local Next.js (Turbopack) 1.351s (-4.1%) 2.004s (~) 0.653s 15 1.14x
💻 Local Express 1.394s (+2.9%) 2.005s (~) 0.611s 15 1.18x
💻 Local Nitro 1.424s (-0.5%) 2.006s (~) 0.581s 15 1.20x
🐘 Postgres Next.js (Turbopack) 2.090s (+0.5%) 2.397s (-12.5% 🟢) 0.307s 13 1.76x
🌐 MongoDB Next.js (Turbopack) 2.154s (~) 3.007s (~) 0.853s 10 1.82x
🐘 Postgres Express 2.516s (+9.0% 🔺) 3.013s (~) 0.497s 10 2.12x
🐘 Postgres Nitro 2.528s (+17.3% 🔺) 3.013s (+19.9% 🔺) 0.485s 10 2.13x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.684s (-5.4% 🟢) 3.801s (+3.0%) 1.117s 8 1.00x
▲ Vercel Nitro 3.089s (+0.7%) 3.890s (-25.9% 🟢) 0.801s 8 1.15x
▲ Vercel Express 3.468s (+11.7% 🔺) 4.332s (+1.7%) 0.864s 7 1.29x

🔍 Observability: Next.js (Turbopack) | Nitro | Express

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 2.367s (-5.2% 🟢) 3.007s (~) 0.641s 10 1.00x
💻 Local Next.js (Turbopack) 2.386s (-0.8%) 3.007s (~) 0.620s 10 1.01x
💻 Local Nitro 2.551s (-0.7%) 3.008s (~) 0.457s 10 1.08x
💻 Local Express 2.601s (+13.5% 🔺) 3.008s (~) 0.407s 10 1.10x
🌐 MongoDB Next.js (Turbopack) 4.656s (-2.3%) 5.177s (~) 0.521s 6 1.97x
🐘 Postgres Nitro 6.872s (-25.2% 🟢) 7.526s (-21.0% 🟢) 0.654s 4 2.90x
🐘 Postgres Express 7.850s (-8.2% 🟢) 8.527s (-5.6% 🟢) 0.677s 4 3.32x
🐘 Postgres Next.js (Turbopack) 12.698s (-2.1%) 13.035s (-2.5%) 0.337s 3 5.37x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.998s (~) 4.017s (-15.2% 🟢) 1.019s 8 1.00x
▲ Vercel Express 3.833s (+25.5% 🔺) 5.113s (+10.0% 🔺) 1.279s 6 1.28x
▲ Vercel Next.js (Turbopack) 4.332s (+24.8% 🔺) 5.451s (+11.5% 🔺) 1.119s 6 1.45x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 3.877s (-4.4%) 4.134s (-12.5% 🟢) 0.256s 8 1.00x
💻 Local Next.js (Turbopack) 6.530s (~) 7.213s (-4.0%) 0.682s 5 1.68x
💻 Local Express 7.096s (+12.5% 🔺) 7.769s (+10.8% 🔺) 0.673s 4 1.83x
💻 Local Nitro 7.345s (-0.6%) 8.021s (~) 0.676s 4 1.89x
🌐 MongoDB Next.js (Turbopack) 9.962s (~) 10.349s (-3.2%) 0.387s 3 2.57x
🐘 Postgres Express 47.122s (-1.0%) 48.117s (~) 0.995s 1 12.15x
🐘 Postgres Nitro 52.033s (+5.3% 🔺) 52.136s (+4.0%) 0.103s 1 13.42x
🐘 Postgres Next.js (Turbopack) 52.210s (-7.4% 🟢) 53.128s (-7.0% 🟢) 0.918s 1 13.47x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 6.263s (+19.0% 🔺) 7.731s (+15.9% 🔺) 1.468s 5 1.00x
▲ Vercel Express 7.160s (+55.1% 🔺) 8.317s (+38.5% 🔺) 1.156s 4 1.14x
▲ Vercel Next.js (Turbopack) 7.385s (+26.2% 🔺) 8.359s (+11.4% 🔺) 0.974s 4 1.18x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 1.214s (-5.0%) 2.006s (~) 0.792s 15 1.00x
💻 Local Next.js (Turbopack) 1.396s (-0.8%) 2.005s (~) 0.609s 15 1.15x
💻 Local Express 1.425s (+5.0% 🔺) 2.006s (~) 0.580s 15 1.17x
💻 Local Nitro 1.440s (+1.9%) 2.005s (~) 0.565s 15 1.19x
🐘 Postgres Nitro 1.961s (-1.8%) 2.397s (-7.8% 🟢) 0.435s 13 1.62x
🐘 Postgres Next.js (Turbopack) 2.020s (-6.7% 🟢) 2.596s (-11.2% 🟢) 0.576s 12 1.66x
🌐 MongoDB Next.js (Turbopack) 2.137s (-1.3%) 3.008s (~) 0.871s 10 1.76x
🐘 Postgres Express 2.350s (+15.2% 🔺) 3.013s (+9.9% 🔺) 0.663s 10 1.94x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.581s (-14.4% 🟢) 3.408s (-23.4% 🟢) 0.827s 10 1.00x
▲ Vercel Express 2.662s (-5.7% 🟢) 3.377s (-12.9% 🟢) 0.715s 9 1.03x
▲ Vercel Nitro 2.977s (+5.1% 🔺) 3.734s (-7.4% 🟢) 0.757s 9 1.15x

🔍 Observability: Next.js (Turbopack) | Express | Nitro

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 2.379s (-6.7% 🟢) 3.008s (~) 0.629s 10 1.00x
💻 Local Next.js (Turbopack) 2.529s (+3.1%) 3.007s (~) 0.478s 10 1.06x
💻 Local Express 2.648s (+10.6% 🔺) 3.007s (~) 0.359s 10 1.11x
💻 Local Nitro 2.688s (+1.7%) 3.008s (~) 0.320s 10 1.13x
🌐 MongoDB Next.js (Turbopack) 4.800s (~) 5.176s (-3.2%) 0.376s 6 2.02x
🐘 Postgres Express 8.409s (-30.5% 🟢) 8.778s (-29.0% 🟢) 0.368s 4 3.54x
🐘 Postgres Nitro 10.231s (-7.7% 🟢) 11.030s (-8.4% 🟢) 0.799s 3 4.30x
🐘 Postgres Next.js (Turbopack) 12.763s (-10.5% 🟢) 13.367s (-7.0% 🟢) 0.604s 3 5.37x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.729s (-14.8% 🟢) 4.113s (-8.2% 🟢) 1.384s 8 1.00x
▲ Vercel Nitro 2.809s (-7.6% 🟢) 4.006s (-12.1% 🟢) 1.197s 8 1.03x
▲ Vercel Next.js (Turbopack) 3.402s (+11.8% 🔺) 4.016s (-1.6%) 0.614s 8 1.25x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 3.847s (-6.3% 🟢) 4.133s (-9.8% 🟢) 0.286s 8 1.00x
💻 Local Next.js (Turbopack) 7.052s (-1.1%) 7.515s (~) 0.463s 4 1.83x
💻 Local Express 7.707s (+6.4% 🔺) 8.022s (~) 0.316s 4 2.00x
💻 Local Nitro 7.989s (+2.9%) 8.770s (+9.3% 🔺) 0.781s 4 2.08x
🌐 MongoDB Next.js (Turbopack) 9.825s (-3.7%) 10.348s (-3.2%) 0.523s 3 2.55x
🐘 Postgres Express 45.430s (-12.0% 🟢) 46.094s (-11.6% 🟢) 0.664s 1 11.81x
🐘 Postgres Nitro 52.347s (-2.3%) 53.112s (-1.9%) 0.765s 1 13.61x
🐘 Postgres Next.js (Turbopack) 56.359s (+3.9%) 57.124s (+3.6%) 0.765s 1 14.65x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.438s (+3.8%) 4.399s (-6.8% 🟢) 0.961s 7 1.00x
▲ Vercel Express 4.197s (+10.9% 🔺) 5.431s (+11.0% 🔺) 1.234s 6 1.22x
▲ Vercel Next.js (Turbopack) 5.191s (+17.4% 🔺) 6.440s (+17.0% 🔺) 1.249s 5 1.51x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 0.109s (-28.6% 🟢) 1.000s (~) 0.001s (-15.4% 🟢) 1.007s (~) 0.898s 10 1.00x
💻 Local Next.js (Turbopack) 0.109s (-25.5% 🟢) 1.001s (~) 0.009s (-8.8% 🟢) 1.014s (~) 0.905s 10 1.00x
💻 Local Nitro 0.174s (-5.7% 🟢) 1.002s (~) 0.011s (+5.7% 🔺) 1.016s (~) 0.843s 10 1.59x
💻 Local Express 0.186s (+68.8% 🔺) 1.003s (~) 0.011s (+10.4% 🔺) 1.016s (~) 0.830s 10 1.71x
🌐 MongoDB Next.js (Turbopack) 0.511s (+1.7%) 0.931s (-1.9%) 0.001s (+9.1% 🔺) 1.008s (~) 0.497s 10 4.68x
🐘 Postgres Next.js (Turbopack) 0.866s (+44.1% 🔺) 0.742s (-24.1% 🟢) 0.001s (-12.5% 🟢) 1.011s (~) 0.144s 10 7.93x
🐘 Postgres Nitro 1.119s (-52.8% 🟢) 1.915s (-24.1% 🟢) 0.001s (-14.3% 🟢) 2.013s (-30.9% 🟢) 0.894s 10 10.24x
🐘 Postgres Express 2.140s (-12.4% 🟢) 2.900s (+11.6% 🔺) 0.001s (+27.3% 🔺) 3.014s (~) 0.874s 10 19.60x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.516s (-10.8% 🟢) 2.928s (-10.9% 🟢) 0.314s (+14.2% 🔺) 3.818s (-10.9% 🟢) 1.301s 10 1.00x
▲ Vercel Express 2.557s (-8.0% 🟢) 2.996s (-10.5% 🟢) 0.564s (+284.4% 🔺) 4.250s (+1.0%) 1.693s 10 1.02x
▲ Vercel Next.js (Turbopack) 2.780s (-2.6%) 3.150s (-12.2% 🟢) 0.235s (-19.5% 🟢) 3.999s (-12.8% 🟢) 1.219s 10 1.10x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Next.js (Turbopack) 11/12
🐘 Postgres Next.js (Turbopack) 6/12
▲ Vercel Nitro 8/12
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 💻 Local 11/12
Next.js (Turbopack) 🌐 Redis 10/12
Nitro 💻 Local 10/12
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run

@vercel
Copy link
Contributor

vercel bot commented Feb 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Feb 12, 2026 8:14pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Feb 12, 2026 8:14pm
example-workflow Ready Ready Preview, Comment Feb 12, 2026 8:14pm
workbench-astro-workflow Ready Ready Preview, Comment Feb 12, 2026 8:14pm
workbench-express-workflow Ready Ready Preview, Comment Feb 12, 2026 8:14pm
workbench-fastify-workflow Ready Ready Preview, Comment Feb 12, 2026 8:14pm
workbench-hono-workflow Ready Ready Preview, Comment Feb 12, 2026 8:14pm
workbench-nitro-workflow Ready Ready Preview, Comment Feb 12, 2026 8:14pm
workbench-nuxt-workflow Ready Ready Preview, Comment Feb 12, 2026 8:14pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Feb 12, 2026 8:14pm
workbench-vite-workflow Ready Ready Preview, Comment Feb 12, 2026 8:14pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Feb 12, 2026 8:14pm
workflow-nest Ready Ready Preview, Comment Feb 12, 2026 8:14pm
workflow-swc-playground Ready Ready Preview, Comment Feb 12, 2026 8:14pm

Copy link
Member Author

TooTallNate commented Feb 11, 2026

@socket-security
Copy link

socket-security bot commented Feb 11, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatednpm/​react-router@​7.10.1 ⏵ 7.13.096100 +2379 +198 +1100
Addednpm/​cross-env@​7.0.310010010082100
Addednpm/​express@​4.22.19710010087100
Addednpm/​isbot@​5.1.3510010010094100

View full report

@TooTallNate TooTallNate changed the title web: replace Next.js with React Router v7 scaffolding Migrate @workflow/web from Next.js to React Router v7 Feb 11, 2026
@TooTallNate TooTallNate changed the title Migrate @workflow/web from Next.js to React Router v7 Migrate @workflow/web from Next.js to React Router v7 Feb 11, 2026
Only console.error for genuine server-side failures (5xx). API-level
client errors (4xx), run-not-found errors, and unrecognized errors
from world backends are not logged server-side — the error is returned
to the caller for handling.
Move the stray-POST handling from individual route actions to a
single catch-all action on root.tsx. This covers all current and
future routes without needing per-route action stubs.
Switch the RPC layer from JSON to CBOR encoding to preserve binary
data (Uint8Array) across the wire. CBOR natively handles binary
types without base64 overhead.

Hydration (deserializing input/output/eventData from binary format
to JS values) stays server-side for now. Making @workflow/core's
hydration code browser-safe requires extracting it from the
serialization module which has deep Node.js dependencies (Buffer,
AsyncLocalStorage, child_process, etc.). This will be addressed
as a prerequisite when e2e encryption lands.

- Add cbor-x as runtime dependency
- RPC route: encode responses as CBOR
- RPC client: send/receive CBOR with proper Content-Type headers
- Server actions: hydrate data before CBOR encoding (no JSON
  round-trip needed since CBOR preserves all JS types)
- Hydration errors are caught and return raw data instead of crashing
- Add error handling for malformed CBOR/JSON request bodies (400)
- Remove unused readStreamServerAction import from RPC route
- Validate streamId format and startIndex parameter in stream route
- Decode CBOR error body in RPC client for better error messages
- Remove duplicate static file middleware from server/app.ts
- Fix dev script to use react-router dev instead of node server.js
Vite's SSR build externalizes certain packages instead of bundling
them. When @workflow/web is installed from npm (not in the monorepo),
these externalized packages must be in dependencies to be available
at runtime. Move all Radix UI, lucide-react, class-variance-authority,
clsx, tailwind-merge, date-fns, next-themes, sonner, @xyflow/react,
and workspace packages to dependencies. Add react/react-dom as
peerDependencies.
Use ssr.noExternal=true in Vite config to bundle all dependencies
into the server build. This means @workflow/web only needs express
at runtime — all UI dependencies (Radix, lucide-react, etc.) are
compiled into the server bundle. Keeps the installed package small.
@TooTallNate TooTallNate force-pushed the nate/migrate-web-to-react-router branch from e89223f to ca37040 Compare February 12, 2026 20:11
karthikscale3 added a commit that referenced this pull request Feb 12, 2026
…transport

Move observability hydration to the shared serialization-format + web/cli-specific hydration modules, shift web UI hydration to the client side, and align RPC/CBOR behavior to reduce merge conflict risk with PR #1005/#1015 while fixing binary payload rendering in observability views.

Co-authored-by: Cursor <cursoragent@cursor.com>
@TooTallNate TooTallNate merged commit 7653e6b into main Feb 13, 2026
152 of 154 checks passed
@TooTallNate TooTallNate deleted the nate/migrate-web-to-react-router branch February 13, 2026 00:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants