refactor(nitro): use nitro v3 functionRules workflow routes - #1575

Merged
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement
May 20, 2026
Merged

refactor(nitro): use nitro v3 functionRules workflow routes#1575
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement

Conversation

@RihanArfan

@RihanArfanRihanArfan commented Apr 1, 2026

Copy link
Copy Markdown
Member

Description

Currently we separately build the Workflows functions for the Vercel Build Output API when deploying a Nitro app to Vercel. This means the Workflows routes aren't part of the Nitro bundle.

This PR changes to now handle the Workflows routes as handlers within Nitro v3. Nitro v2 is untouched.

How did you test your changes?

Locally ran nitro dev in the nitro-v3 workspace and visited the API route POST /api/test-direct-step-call.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR
    • During beta, we only use "patch" mode for changes. Don't tag minor/major versions.
    • Use pnpm changeset --empty if you are changing documentation or workbench apps
  • 🔒 DCO sign-off passes (run git commit --signoff on your commits)
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete

@changeset-bot

changeset-botBot commented Apr 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2c843be

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

This PR includes changesets to release 17 packages
NameType
@workflow/nitroPatch
@workflow/nuxtPatch
workflowPatch
@workflow/aiPatch
@workflow/world-testingPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Comment threadpackages/nitro/src/index.ts
@RihanArfan
RihanArfanforce-pushed the feat/nitro-builder-improvement branch from e312e97 to 3eb081aCompareApril 7, 2026 10:28
@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.030s (-29.7% 🟢)1.006s (~)0.975s101.00x
💻 LocalExpress0.031s (-29.6% 🟢)1.005s (~)0.974s101.03x
🐘 PostgresExpress0.043s (-25.5% 🟢)1.010s (~)0.967s101.43x
🐘 PostgresNitro0.048s (-49.2% 🟢)1.010s (-3.1%)0.962s101.60x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.224s (-10.9% 🟢)1.890s (-19.0% 🟢)1.666s101.00x
▲ VercelNitro0.225s (-45.0% 🟢)2.732s (+8.9% 🔺)2.507s101.01x
▲ VercelExpress0.397s (+68.6% 🔺)2.027s (-5.1% 🟢)1.630s101.77x

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

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.072s (-5.3% 🟢)2.007s (~)0.935s101.00x
💻 LocalExpress1.075s (-4.5%)2.006s (~)0.931s101.00x
🐘 PostgresExpress1.082s (-5.6% 🟢)2.009s (~)0.927s101.01x
🐘 PostgresNitro1.083s (-5.0%)2.010s (~)0.927s101.01x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.539s (-17.9% 🟢)3.295s (-13.5% 🟢)1.756s101.00x
▲ VercelNext.js (Turbopack)1.689s (-17.0% 🟢)4.668s (+21.9% 🔺)2.979s101.10x
▲ VercelNitro2.027s (-47.9% 🟢)3.895s (-34.1% 🟢)1.869s101.32x

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

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.412s (-4.7%)11.021s (~)0.608s31.00x
🐘 PostgresExpress10.416s (-5.0%)11.016s (~)0.600s31.00x
💻 LocalNitro10.424s (-4.8%)11.021s (~)0.598s31.00x
🐘 PostgresNitro10.433s (-4.0%)11.020s (~)0.586s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)13.515s (-22.0% 🟢)15.908s (-18.0% 🟢)2.392s21.00x
▲ VercelExpress13.568s (-20.1% 🟢)15.557s (-22.3% 🟢)1.989s21.00x
▲ VercelNitro13.587s (-42.7% 🟢)15.559s (-38.1% 🟢)1.972s21.01x

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

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.456s (-7.7% 🟢)14.017s (-6.7% 🟢)0.561s51.00x
💻 LocalExpress13.459s (-10.1% 🟢)14.025s (-6.7% 🟢)0.566s51.00x
🐘 PostgresNitro13.466s (-7.7% 🟢)14.019s (-6.7% 🟢)0.553s51.00x
💻 LocalNitro13.479s (-10.5% 🟢)14.027s (-12.5% 🟢)0.548s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express20.282s (-59.7% 🟢)22.320s (-57.5% 🟢)2.038s31.00x
▲ VercelNext.js (Turbopack)20.648s (-60.7% 🟢)22.415s (-59.0% 🟢)1.768s31.02x
▲ VercelNitro20.776s (-67.8% 🟢)22.345s (-66.5% 🟢)1.569s31.02x

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

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express11.896s (-28.3% 🟢)12.020s (-29.4% 🟢)0.124s81.00x
💻 LocalNitro11.919s (-29.0% 🟢)12.022s (-29.4% 🟢)0.103s81.00x
🐘 PostgresExpress11.979s (-14.5% 🟢)12.268s (-15.9% 🟢)0.288s81.01x
🐘 PostgresNitro12.158s (-12.9% 🟢)12.872s (-10.0% 🟢)0.714s71.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express30.115s (-75.2% 🟢)32.130s (-74.0% 🟢)2.014s31.00x
▲ VercelNitro30.529s (-92.8% 🟢)32.167s (-92.4% 🟢)1.638s31.01x
▲ VercelNext.js (Turbopack)31.252s (-92.1% 🟢)32.624s (-91.7% 🟢)1.372s31.04x

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

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.138s (-9.7% 🟢)2.007s (~)0.870s151.00x
💻 LocalExpress1.166s (-21.7% 🟢)2.006s (~)0.840s151.02x
💻 LocalNitro1.175s (-28.0% 🟢)2.006s (-3.3%)0.830s151.03x
🐘 PostgresNitro1.238s (-2.9%)2.074s (+3.2%)0.836s151.09x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.605s (-7.5% 🟢)3.989s (-7.7% 🟢)1.383s91.00x
▲ VercelExpress2.632s (-8.0% 🟢)4.521s (-2.2%)1.889s71.01x
▲ VercelNext.js (Turbopack)2.660s (-21.7% 🟢)4.213s (-14.6% 🟢)1.553s81.02x

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

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.199s (-49.2% 🟢)2.006s (-33.3% 🟢)0.807s151.00x
🐘 PostgresNitro1.234s (-47.5% 🟢)2.008s (-33.3% 🟢)0.773s151.03x
💻 LocalExpress1.611s (-45.5% 🟢)2.006s (-41.9% 🟢)0.395s151.34x
💻 LocalNitro1.781s (-43.3% 🟢)2.005s (-48.4% 🟢)0.224s151.49x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.495s (-3.4%)5.288s (+3.5%)1.793s61.00x
▲ VercelNext.js (Turbopack)3.647s (-48.6% 🟢)5.434s (-39.0% 🟢)1.787s61.04x
▲ VercelNitro4.003s (-1.2%)5.826s (-1.6%)1.823s61.15x

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

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.290s (-63.0% 🟢)2.007s (-50.0% 🟢)0.717s151.00x
🐘 PostgresNitro1.350s (-61.2% 🟢)2.008s (-49.9% 🟢)0.659s151.05x
💻 LocalExpress4.222s (-49.4% 🟢)4.582s (-49.2% 🟢)0.361s73.27x
💻 LocalNitro5.125s (-38.6% 🟢)5.678s (-37.1% 🟢)0.554s63.97x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.678s (+32.7% 🔺)6.756s (+22.1% 🔺)2.078s51.00x
▲ VercelExpress6.267s (+47.8% 🔺)8.267s (+34.9% 🔺)2.000s41.34x
▲ VercelNext.js (Turbopack)7.564s (-15.2% 🟢)9.835s (-10.3% 🟢)2.271s41.62x

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

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.145s (-8.9% 🟢)2.008s (~)0.863s151.00x
🐘 PostgresNitro1.149s (-8.6% 🟢)2.008s (~)0.859s151.00x
💻 LocalExpress1.361s (-28.1% 🟢)2.006s (-15.1% 🟢)0.645s151.19x
💻 LocalNitro1.371s (-26.5% 🟢)2.006s (-14.3% 🟢)0.635s151.20x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.498s (+1.6%)4.099s (-1.7%)1.601s81.00x
▲ VercelNext.js (Turbopack)2.715s (-7.4% 🟢)4.421s (-4.8%)1.707s71.09x
▲ VercelExpress2.897s (+12.2% 🔺)4.519s (+3.9%)1.622s71.16x

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

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.192s (-49.1% 🟢)2.008s (-33.3% 🟢)0.817s151.00x
🐘 PostgresExpress1.198s (-48.8% 🟢)2.009s (-33.3% 🟢)0.811s151.01x
💻 LocalExpress1.702s (-45.7% 🟢)2.008s (-46.6% 🟢)0.306s151.43x
💻 LocalNitro2.025s (-33.9% 🟢)2.391s (-38.5% 🟢)0.366s131.70x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.357s (+3.8%)5.248s (+3.4%)1.892s61.00x
▲ VercelExpress3.634s (+13.8% 🔺)5.286s (+10.3% 🔺)1.652s61.08x
▲ VercelNext.js (Turbopack)4.356s (+38.6% 🔺)6.224s (+37.6% 🔺)1.868s61.30x

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

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.271s (-63.7% 🟢)2.007s (-50.0% 🟢)0.735s151.00x
🐘 PostgresNitro1.304s (-62.5% 🟢)2.007s (-49.9% 🟢)0.703s151.03x
💻 LocalExpress4.961s (-43.6% 🟢)5.514s (-40.5% 🟢)0.553s63.90x
💻 LocalNitro6.189s (-32.3% 🟢)6.415s (-36.0% 🟢)0.226s54.87x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.914s (-3.5%)6.839s (~)1.925s51.00x
▲ VercelExpress5.278s (-17.7% 🟢)7.562s (-7.5% 🟢)2.284s41.07x
▲ VercelNext.js (Turbopack)6.788s (~)8.497s (-0.5%)1.709s41.38x

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

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.434s (-48.2% 🟢)1.006s (-1.6%)0.572s601.00x
🐘 PostgresNitro0.454s (-44.7% 🟢)1.024s (+1.8%)0.570s591.05x
💻 LocalNitro0.485s (-50.5% 🟢)1.004s (-8.2% 🟢)0.519s601.12x
💻 LocalExpress0.563s (-42.7% 🟢)1.092s (+1.5%)0.528s571.30x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.763s (-78.4% 🟢)6.470s (-73.1% 🟢)1.707s101.00x
▲ VercelNext.js (Turbopack)5.279s (-63.6% 🟢)6.979s (-56.6% 🟢)1.700s91.11x
▲ VercelExpress5.386s (-71.7% 🟢)7.590s (-64.4% 🟢)2.204s81.13x

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

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.038s (-46.1% 🟢)1.791s (-14.7% 🟢)0.753s511.00x
🐘 PostgresExpress1.094s (-44.7% 🟢)1.705s (-24.5% 🟢)0.611s531.05x
💻 LocalExpress1.163s (-61.4% 🟢)2.005s (-44.1% 🟢)0.842s451.12x
💻 LocalNitro1.200s (-60.5% 🟢)2.006s (-46.6% 🟢)0.806s451.16x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.272s (-68.9% 🟢)14.115s (-65.8% 🟢)1.842s71.00x
▲ VercelNext.js (Turbopack)13.606s (-72.7% 🟢)15.509s (-70.0% 🟢)1.903s61.11x
▲ VercelExpress13.810s (-60.0% 🟢)16.683s (-54.7% 🟢)2.872s61.13x

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

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.047s (-48.7% 🟢)2.506s (-42.6% 🟢)0.459s481.00x
🐘 PostgresNitro2.132s (-48.0% 🟢)2.798s (-39.2% 🟢)0.666s431.04x
💻 LocalExpress2.603s (-71.7% 🟢)3.007s (-70.0% 🟢)0.404s401.27x
💻 LocalNitro2.699s (-71.0% 🟢)3.008s (-70.0% 🟢)0.308s401.32x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro39.315s (-59.4% 🟢)41.156s (-58.2% 🟢)1.841s31.00x
▲ VercelExpress40.781s (-68.6% 🟢)43.325s (-67.2% 🟢)2.544s31.04x
▲ VercelNext.js (Turbopack)41.245s (-61.5% 🟢)42.926s (-60.6% 🟢)1.681s31.05x

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

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.165s (-41.6% 🟢)1.006s (~)0.841s601.00x
🐘 PostgresNitro0.172s (-39.4% 🟢)1.006s (~)0.834s601.04x
💻 LocalExpress0.421s (-24.9% 🟢)1.004s (~)0.583s602.55x
💻 LocalNitro0.463s (-23.5% 🟢)1.004s (-1.7%)0.541s602.81x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.161s (+10.6% 🔺)4.409s (+21.2% 🔺)2.249s141.00x
▲ VercelNext.js (Turbopack)2.599s (+28.5% 🔺)4.308s (+13.6% 🔺)1.709s141.20x
▲ VercelNitro2.656s (+59.9% 🔺)4.513s (+34.7% 🔺)1.858s141.23x

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

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.285s (-44.0% 🟢)1.006s (~)0.720s901.00x
🐘 PostgresNitro0.298s (-40.0% 🟢)1.006s (~)0.708s901.04x
💻 LocalExpress2.156s (-14.2% 🟢)2.685s (-10.8% 🟢)0.529s347.56x
💻 LocalNitro2.179s (-14.2% 🟢)2.912s (-3.2%)0.733s317.64x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)8.136s (+130.1% 🔺)10.219s (+96.8% 🔺)2.083s91.00x
▲ VercelExpress8.249s (+170.8% 🔺)10.683s (+122.2% 🔺)2.434s91.01x
▲ VercelNitro9.696s (+200.6% 🔺)11.640s (+141.4% 🔺)1.944s81.19x

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

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.558s (-31.8% 🟢)1.005s (-1.2%)0.447s1201.00x
🐘 PostgresNitro0.652s (-17.5% 🟢)1.006s (~)0.354s1201.17x
💻 LocalExpress9.153s (-18.2% 🟢)9.795s (-18.0% 🟢)0.643s1316.40x
💻 LocalNitro10.117s (-9.6% 🟢)10.778s (-7.6% 🟢)0.661s1218.13x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.676s (+119.6% 🔺)24.944s (+103.0% 🔺)2.268s51.00x
▲ VercelNitro23.736s (+207.4% 🔺)25.097s (+167.0% 🔺)1.361s51.05x
▲ VercelExpress28.260s (+280.9% 🔺)30.473s (+229.6% 🔺)2.213s41.25x

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

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.126s (+465.4% 🔺)2.004s (+99.5% 🔺)0.010s (-19.8% 🟢)2.016s (+98.0% 🔺)0.890s101.00x
🐘 PostgresExpress1.133s (+452.5% 🔺)2.002s (+100.5% 🔺)0.001s (-25.0% 🟢)2.010s (+98.8% 🔺)0.877s101.01x
💻 LocalNitro1.143s (+434.8% 🔺)2.005s (+99.6% 🔺)0.012s (-2.4%)2.019s (+98.2% 🔺)0.877s101.02x
🐘 PostgresNitro1.157s (+464.4% 🔺)1.999s (+100.0% 🔺)0.001s (-20.0% 🟢)2.010s (+98.8% 🔺)0.853s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)2.185s (-68.1% 🟢)3.165s (-63.4% 🟢)1.925s (+204.7% 🔺)5.496s (-43.9% 🟢)3.311s101.00x
▲ VercelNitro2.295s (-40.1% 🟢)3.603s (-31.7% 🟢)13.820s (+1762.3% 🔺)17.886s (+175.9% 🔺)15.591s101.05x
▲ VercelExpress2.329s (-7.0% 🟢)3.832s (-6.3% 🟢)1.518s (+58.0% 🔺)5.905s (+5.6% 🔺)3.576s101.07x

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

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.490s (+136.5% 🔺)2.005s (+99.2% 🔺)0.003s (-12.1% 🟢)2.022s (+97.7% 🔺)0.532s301.00x
🐘 PostgresNitro1.528s (+144.8% 🔺)2.003s (+98.9% 🔺)0.004s (-3.3%)2.027s (+98.2% 🔺)0.499s301.03x
💻 LocalNitro1.707s (+103.5% 🔺)2.011s (+98.7% 🔺)0.010s (+6.7% 🔺)2.202s (+97.3% 🔺)0.495s281.15x
💻 LocalExpress1.877s (+148.0% 🔺)2.011s (+95.5% 🔺)0.009s (-0.9%)2.394s (+130.2% 🔺)0.517s271.26x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.935s (-8.8% 🟢)7.705s (-3.8%)0.216s (-47.0% 🟢)8.447s (-4.4%)2.512s81.00x
▲ VercelNitro5.975s (-79.7% 🟢)7.548s (-75.5% 🟢)0.263s (+134.7% 🔺)8.277s (-74.0% 🟢)2.302s81.01x
▲ VercelNext.js (Turbopack)6.750s (-60.1% 🟢)7.706s (-57.8% 🟢)0.297s (+40.7% 🔺)8.648s (-54.3% 🟢)1.898s71.14x

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

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.637s (-34.3% 🟢)1.034s (-17.1% 🟢)0.000s (-58.6% 🟢)1.042s (-17.2% 🟢)0.405s581.00x
🐘 PostgresExpress0.647s (-32.7% 🟢)1.050s (-17.8% 🟢)0.000s (+58.6% 🔺)1.060s (-18.8% 🟢)0.413s581.02x
💻 LocalExpress1.339s (+9.3% 🔺)2.016s (~)0.001s (+70.0% 🔺)2.018s (~)0.679s302.10x
💻 LocalNitro1.726s (+41.2% 🔺)2.015s (~)0.000s (+140.0% 🔺)2.418s (+19.6% 🔺)0.691s252.71x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)3.534s (-65.3% 🟢)4.591s (-60.1% 🟢)0.000s (NaN%)5.094s (-57.7% 🟢)1.560s121.00x
▲ VercelNitro3.538s (+16.0% 🔺)4.997s (+13.8% 🔺)0.000s (+225.0% 🔺)5.453s (+13.4% 🔺)1.916s121.00x
▲ VercelExpress3.646s (-2.5%)5.083s (~)0.000s (~)5.696s (+3.0%)2.050s111.03x

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

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.294s (-27.0% 🟢)1.905s (-12.5% 🟢)0.000s (NaN%)1.924s (-12.5% 🟢)0.630s321.00x
🐘 PostgresNitro1.320s (-26.3% 🟢)2.031s (-5.1% 🟢)0.000s (-100.0% 🟢)2.043s (-6.0% 🟢)0.723s301.02x
💻 LocalExpress3.031s (-12.6% 🟢)3.777s (-6.4% 🟢)0.001s (-21.9% 🟢)3.780s (-6.3% 🟢)0.749s162.34x
💻 LocalNitro3.076s (-9.2% 🟢)3.777s (-6.3% 🟢)0.001s (-6.2% 🟢)3.779s (-6.4% 🟢)0.703s162.38x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.447s (+33.1% 🔺)7.047s (+31.1% 🔺)0.000s (-54.2% 🟢)7.517s (+29.7% 🔺)2.070s81.00x
▲ VercelNext.js (Turbopack)5.517s (-1.8%)7.043s (+0.9%)0.000s (~)7.537s (~)2.020s81.01x
▲ VercelExpress6.217s (+35.5% 🔺)7.655s (+27.1% 🔺)0.000s (NaN%)8.501s (+31.7% 🔺)2.284s81.14x

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

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress17/21
🐘 PostgresExpress18/21
▲ VercelNitro9/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)▲ Vercel21/21
Nitro🐘 Postgres15/21
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)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production120002191419
✅ 💻 Local Development158702191806
✅ 📦 Local Production158702191806
✅ 🐘 Local Postgres158702191806
✅ 🪟 Windows12900129
✅ 📋 Other7270176903
Total6817010527869

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro103026
✅ example103026
✅ express103026
✅ fastify103026
✅ hono103026
✅ nextjs-turbopack12702
✅ nextjs-webpack12702
✅ nitro103026
✅ nuxt103026
✅ sveltekit12207
✅ vite103026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack12900
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable104025
✅ e2e-local-dev-tanstack-start-104025
✅ e2e-local-postgres-nest-stable104025
✅ e2e-local-postgres-tanstack-start-104025
✅ e2e-local-prod-nest-stable104025
✅ e2e-local-prod-tanstack-start-104025
✅ e2e-vercel-prod-tanstack-start103026

📋 View full workflow run

Comment threadpackages/nitro/src/index.ts Outdated
@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably know better than me whether the nits apply, so use your judgement there. Happy to approve this once the e2e tests are running again and CI is green

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: blocking issues found

Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.test.ts
Comment threadpackages/builders/src/types.ts Outdated
* Extra esbuild options merged into every bundle this builder produces
* (steps, intermediate workflow, final workflow wrapper, webhook).
*/
esbuildOptions?: Partial<BuildOptions>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

esbuildOptions: Partial<BuildOptions> is a broad surface for one knob

The new field accepts the full esbuild BuildOptions and is spread into four bundle calls with very different needs (steps: node/esm, workflow VM: neutral/cjs, webhook: node/esm, final wrapper: node/esm). The current spread order puts user options first so builder-required options like platform, format, bundle, plugins win — that's defensive — but the API still implies "you can pass any esbuild option" when in practice almost everything that matters is overridden.

Given the only consumer is sourcesContent: false, consider either narrowing the public type to a small allowlist or naming the field something closer to its actual purpose. Not blocking, but the surface area will be hard to take back later.

@VaguelySerious

Copy link
Copy Markdown
Member

Seems like tests are still failing, please re-ping me when they pass 👋

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

Hey @VaguelySerious, the related tests are passing 🥳

@vercel

vercelBot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Deployment failed with the following error:

The Preview Deployment Suffix domain "labs.vercel.dev" is not available under the current scope. Learn more: https://vercel.link/3a1

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: no blocking issues

Comment threadpackages/nitro/src/index.ts Outdated
};

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The webhook handler is registered at /.well-known/workflow/v1/webhook/:token (line 214), but this functionRules key uses webhook/**. I built workbench/nitro-v3 with NITRO_PRESET=vercel and workflow.runtime: 'nodejs22.x' and verified that nitro's vercel preset generates two function dirs in this case:

  • webhook/[token].func — created via routeFuncRouter.match against the :token route, gets the runtime override.
  • webhook/[...].func — created from this ** rule, also gets the runtime override.

In config.json, the webhook/(?:.*) route emitted by this rule comes before webhook/(?<token>[^/]+), so [token].func ends up unreachable. The result is a wasted function dir (extra disk/build/deploy size) and slightly looser routing (/webhook/ with no token now matches).

Suggested fix: use the same pattern as the handler, e.g. '/.well-known/workflow/v1/webhook/:token'. With matching keys, nitro reuses the single funcDir under the runtime override.

// V2 combined: a single trigger covers both `__wkf_workflow_*`
// (workflow orchestration) and `__wkf_step_*` (step execution),
// since the same handler dispatches both.
experimentalTriggers: [WORKFLOW_QUEUE_TRIGGER],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The comment two lines above says "merged with any user-defined rules at the same paths so explicit user config (e.g. memory) is preserved", but the spread order here (user first, then maxDuration / experimentalTriggers) means workflow's values always win. Only fields that workflow doesn't touch (like memory) survive — user-set maxDuration, runtime, or additional experimentalTriggers get clobbered.

Two small things:

  1. The comment should match the behavior — something like "workflow's required triggers/maxDuration take precedence; unrelated fields like memory are preserved."
  2. Other integrations (e.g. packages/sveltekit) concatenate user triggers rather than replacing them. Probably not an issue in practice since this is a workflow-owned route, but worth deciding deliberately.

Also (related): also exercised by the E2E Vercel Prod Tests (hono) job, which currently fails on maxRetries=0 disables retries (60s timeout) on this PR while passing on main. And E2E Local Prod Tests (tanstack-start) fails on abortVoidSleepTimeoutWorkflow (also passes on main). Both look like potential regressions worth investigating before merge — they aren't in the known-flaky set.

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';
rules[webhookPath] = { ...rules[webhookPath], runtime };
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

runtime is propagated to the flow and webhook routes but not to the manifest route (/.well-known/workflow/v1/manifest.json, registered via addManifestHandler). If a user picks a non-default runtime, the manifest gets served from the catch-all __server function on the default runtime. Low impact (manifest is static JSON), but inconsistent — worth either documenting why it's intentionally excluded, or applying the same override.

return majorVersion === 2;
}
return !nitro.routing;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

This PR adds non-trivial new logic (isNitroV2, the useLegacyVercelBuild branch, the functionRules merge) with no unit test coverage.

Also heads-up: the existing packages/nitro/src/index.test.ts is already broken on main (2 of 10 tests reference #workflow/steps.mjs, which no longer exists since the V1→V2 combined bundle change). Not introduced by this PR, but since the file is already touching the same area, fixing those tests and adding a couple of cases for isNitroV2 and the v3-vercel branch would be a low-cost addition.

Comment threadpackages/nitro/src/builders.ts Outdated
*/
function getNitroStringExternals(nitro: Nitro): string[] | undefined {
const externals = nitro.options.externals?.external?.filter(
const external = (nitro.options as { externals?: { external?: unknown[] } })

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

The structural cast widens external to unknown[] then filters strings — works, but a named alias would read better:

typeNitroV2Options={externals?: {external?: unknown[]}};constexternal=(nitro.optionsasNitroV2Options).externals?.external;

Non-blocking.

Comment threadworkbench/vite/vite.config.ts
The functionRule was keyed by `webhook/**` while the actual handler is
registered at `webhook/:token`, so nitro v3's vercel preset created a
second `webhook/[...].func` next to the real one and shadowed it in the
generated routes. Use the same `:token` pattern as the handler so the
runtime override applies to the function that actually serves traffic.
Also propagate `workflow.runtime` to the public manifest route for
consistency, clarify the merge-precedence comment, and refresh the unit
tests to cover the v3 functionRules path and the `isNitroV2` detection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Wielander <peter.wielander@vercel.com>
@VaguelySerious

Copy link
Copy Markdown
Member

auto-merge is on, waiting for CI to pass

Comment threadpnpm-workspace.yaml
enhanced-resolve: 5.19.0
esbuild: ^0.27.3
nitro: 3.0.1-alpha.1
nitro: ^3.0.260429-beta

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should nitro on the workbench not be a stable release? 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro v2 is nitropack and Nitro v3 is nitro so I think it should be fine. Most the things target Nitro v3 although it's not got a "stable" release yet till Nuxt v5.

@github-actionsgithub-actionsBot mentioned this pull request May 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for c1242e8 (AI decision).

This commit refactors @workflow/nitro to use Nitro v3's functionRules for workflow routes, a code path that does not exist on stable (stable still uses the legacy VercelBuilder for all Vercel deploys regardless of Nitro version). It also bumps the nitro catalog dependency from 3.0.1-alpha.1 to ^3.0.260429-beta, which is a substantial upstream change tied to features only used on main. The webhook pattern fix called out in the changeset only applies to the v3 functionRules path that isn't present on stable.

To override, re-run the Backport to stable workflow manually via workflow_dispatch with this commit SHA.

Sign up for freeto 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.

3 participants

@RihanArfan@VaguelySerious@pi0
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

refactor(nitro): use nitro v3 functionRules workflow routes - #1575

Merged
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement
May 20, 2026
Merged

refactor(nitro): use nitro v3 functionRules workflow routes#1575
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement

Conversation

@RihanArfan

@RihanArfanRihanArfan commented Apr 1, 2026

Copy link
Copy Markdown
Member

Description

Currently we separately build the Workflows functions for the Vercel Build Output API when deploying a Nitro app to Vercel. This means the Workflows routes aren't part of the Nitro bundle.

This PR changes to now handle the Workflows routes as handlers within Nitro v3. Nitro v2 is untouched.

How did you test your changes?

Locally ran nitro dev in the nitro-v3 workspace and visited the API route POST /api/test-direct-step-call.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR
    • During beta, we only use "patch" mode for changes. Don't tag minor/major versions.
    • Use pnpm changeset --empty if you are changing documentation or workbench apps
  • 🔒 DCO sign-off passes (run git commit --signoff on your commits)
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete

@changeset-bot

changeset-botBot commented Apr 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2c843be

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

This PR includes changesets to release 17 packages
NameType
@workflow/nitroPatch
@workflow/nuxtPatch
workflowPatch
@workflow/aiPatch
@workflow/world-testingPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Comment threadpackages/nitro/src/index.ts
@RihanArfan
RihanArfanforce-pushed the feat/nitro-builder-improvement branch from e312e97 to 3eb081aCompareApril 7, 2026 10:28
@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.030s (-29.7% 🟢)1.006s (~)0.975s101.00x
💻 LocalExpress0.031s (-29.6% 🟢)1.005s (~)0.974s101.03x
🐘 PostgresExpress0.043s (-25.5% 🟢)1.010s (~)0.967s101.43x
🐘 PostgresNitro0.048s (-49.2% 🟢)1.010s (-3.1%)0.962s101.60x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.224s (-10.9% 🟢)1.890s (-19.0% 🟢)1.666s101.00x
▲ VercelNitro0.225s (-45.0% 🟢)2.732s (+8.9% 🔺)2.507s101.01x
▲ VercelExpress0.397s (+68.6% 🔺)2.027s (-5.1% 🟢)1.630s101.77x

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

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.072s (-5.3% 🟢)2.007s (~)0.935s101.00x
💻 LocalExpress1.075s (-4.5%)2.006s (~)0.931s101.00x
🐘 PostgresExpress1.082s (-5.6% 🟢)2.009s (~)0.927s101.01x
🐘 PostgresNitro1.083s (-5.0%)2.010s (~)0.927s101.01x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.539s (-17.9% 🟢)3.295s (-13.5% 🟢)1.756s101.00x
▲ VercelNext.js (Turbopack)1.689s (-17.0% 🟢)4.668s (+21.9% 🔺)2.979s101.10x
▲ VercelNitro2.027s (-47.9% 🟢)3.895s (-34.1% 🟢)1.869s101.32x

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

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.412s (-4.7%)11.021s (~)0.608s31.00x
🐘 PostgresExpress10.416s (-5.0%)11.016s (~)0.600s31.00x
💻 LocalNitro10.424s (-4.8%)11.021s (~)0.598s31.00x
🐘 PostgresNitro10.433s (-4.0%)11.020s (~)0.586s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)13.515s (-22.0% 🟢)15.908s (-18.0% 🟢)2.392s21.00x
▲ VercelExpress13.568s (-20.1% 🟢)15.557s (-22.3% 🟢)1.989s21.00x
▲ VercelNitro13.587s (-42.7% 🟢)15.559s (-38.1% 🟢)1.972s21.01x

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

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.456s (-7.7% 🟢)14.017s (-6.7% 🟢)0.561s51.00x
💻 LocalExpress13.459s (-10.1% 🟢)14.025s (-6.7% 🟢)0.566s51.00x
🐘 PostgresNitro13.466s (-7.7% 🟢)14.019s (-6.7% 🟢)0.553s51.00x
💻 LocalNitro13.479s (-10.5% 🟢)14.027s (-12.5% 🟢)0.548s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express20.282s (-59.7% 🟢)22.320s (-57.5% 🟢)2.038s31.00x
▲ VercelNext.js (Turbopack)20.648s (-60.7% 🟢)22.415s (-59.0% 🟢)1.768s31.02x
▲ VercelNitro20.776s (-67.8% 🟢)22.345s (-66.5% 🟢)1.569s31.02x

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

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express11.896s (-28.3% 🟢)12.020s (-29.4% 🟢)0.124s81.00x
💻 LocalNitro11.919s (-29.0% 🟢)12.022s (-29.4% 🟢)0.103s81.00x
🐘 PostgresExpress11.979s (-14.5% 🟢)12.268s (-15.9% 🟢)0.288s81.01x
🐘 PostgresNitro12.158s (-12.9% 🟢)12.872s (-10.0% 🟢)0.714s71.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express30.115s (-75.2% 🟢)32.130s (-74.0% 🟢)2.014s31.00x
▲ VercelNitro30.529s (-92.8% 🟢)32.167s (-92.4% 🟢)1.638s31.01x
▲ VercelNext.js (Turbopack)31.252s (-92.1% 🟢)32.624s (-91.7% 🟢)1.372s31.04x

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

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.138s (-9.7% 🟢)2.007s (~)0.870s151.00x
💻 LocalExpress1.166s (-21.7% 🟢)2.006s (~)0.840s151.02x
💻 LocalNitro1.175s (-28.0% 🟢)2.006s (-3.3%)0.830s151.03x
🐘 PostgresNitro1.238s (-2.9%)2.074s (+3.2%)0.836s151.09x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.605s (-7.5% 🟢)3.989s (-7.7% 🟢)1.383s91.00x
▲ VercelExpress2.632s (-8.0% 🟢)4.521s (-2.2%)1.889s71.01x
▲ VercelNext.js (Turbopack)2.660s (-21.7% 🟢)4.213s (-14.6% 🟢)1.553s81.02x

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

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.199s (-49.2% 🟢)2.006s (-33.3% 🟢)0.807s151.00x
🐘 PostgresNitro1.234s (-47.5% 🟢)2.008s (-33.3% 🟢)0.773s151.03x
💻 LocalExpress1.611s (-45.5% 🟢)2.006s (-41.9% 🟢)0.395s151.34x
💻 LocalNitro1.781s (-43.3% 🟢)2.005s (-48.4% 🟢)0.224s151.49x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.495s (-3.4%)5.288s (+3.5%)1.793s61.00x
▲ VercelNext.js (Turbopack)3.647s (-48.6% 🟢)5.434s (-39.0% 🟢)1.787s61.04x
▲ VercelNitro4.003s (-1.2%)5.826s (-1.6%)1.823s61.15x

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

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.290s (-63.0% 🟢)2.007s (-50.0% 🟢)0.717s151.00x
🐘 PostgresNitro1.350s (-61.2% 🟢)2.008s (-49.9% 🟢)0.659s151.05x
💻 LocalExpress4.222s (-49.4% 🟢)4.582s (-49.2% 🟢)0.361s73.27x
💻 LocalNitro5.125s (-38.6% 🟢)5.678s (-37.1% 🟢)0.554s63.97x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.678s (+32.7% 🔺)6.756s (+22.1% 🔺)2.078s51.00x
▲ VercelExpress6.267s (+47.8% 🔺)8.267s (+34.9% 🔺)2.000s41.34x
▲ VercelNext.js (Turbopack)7.564s (-15.2% 🟢)9.835s (-10.3% 🟢)2.271s41.62x

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

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.145s (-8.9% 🟢)2.008s (~)0.863s151.00x
🐘 PostgresNitro1.149s (-8.6% 🟢)2.008s (~)0.859s151.00x
💻 LocalExpress1.361s (-28.1% 🟢)2.006s (-15.1% 🟢)0.645s151.19x
💻 LocalNitro1.371s (-26.5% 🟢)2.006s (-14.3% 🟢)0.635s151.20x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.498s (+1.6%)4.099s (-1.7%)1.601s81.00x
▲ VercelNext.js (Turbopack)2.715s (-7.4% 🟢)4.421s (-4.8%)1.707s71.09x
▲ VercelExpress2.897s (+12.2% 🔺)4.519s (+3.9%)1.622s71.16x

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

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.192s (-49.1% 🟢)2.008s (-33.3% 🟢)0.817s151.00x
🐘 PostgresExpress1.198s (-48.8% 🟢)2.009s (-33.3% 🟢)0.811s151.01x
💻 LocalExpress1.702s (-45.7% 🟢)2.008s (-46.6% 🟢)0.306s151.43x
💻 LocalNitro2.025s (-33.9% 🟢)2.391s (-38.5% 🟢)0.366s131.70x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.357s (+3.8%)5.248s (+3.4%)1.892s61.00x
▲ VercelExpress3.634s (+13.8% 🔺)5.286s (+10.3% 🔺)1.652s61.08x
▲ VercelNext.js (Turbopack)4.356s (+38.6% 🔺)6.224s (+37.6% 🔺)1.868s61.30x

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

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.271s (-63.7% 🟢)2.007s (-50.0% 🟢)0.735s151.00x
🐘 PostgresNitro1.304s (-62.5% 🟢)2.007s (-49.9% 🟢)0.703s151.03x
💻 LocalExpress4.961s (-43.6% 🟢)5.514s (-40.5% 🟢)0.553s63.90x
💻 LocalNitro6.189s (-32.3% 🟢)6.415s (-36.0% 🟢)0.226s54.87x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.914s (-3.5%)6.839s (~)1.925s51.00x
▲ VercelExpress5.278s (-17.7% 🟢)7.562s (-7.5% 🟢)2.284s41.07x
▲ VercelNext.js (Turbopack)6.788s (~)8.497s (-0.5%)1.709s41.38x

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

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.434s (-48.2% 🟢)1.006s (-1.6%)0.572s601.00x
🐘 PostgresNitro0.454s (-44.7% 🟢)1.024s (+1.8%)0.570s591.05x
💻 LocalNitro0.485s (-50.5% 🟢)1.004s (-8.2% 🟢)0.519s601.12x
💻 LocalExpress0.563s (-42.7% 🟢)1.092s (+1.5%)0.528s571.30x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.763s (-78.4% 🟢)6.470s (-73.1% 🟢)1.707s101.00x
▲ VercelNext.js (Turbopack)5.279s (-63.6% 🟢)6.979s (-56.6% 🟢)1.700s91.11x
▲ VercelExpress5.386s (-71.7% 🟢)7.590s (-64.4% 🟢)2.204s81.13x

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

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.038s (-46.1% 🟢)1.791s (-14.7% 🟢)0.753s511.00x
🐘 PostgresExpress1.094s (-44.7% 🟢)1.705s (-24.5% 🟢)0.611s531.05x
💻 LocalExpress1.163s (-61.4% 🟢)2.005s (-44.1% 🟢)0.842s451.12x
💻 LocalNitro1.200s (-60.5% 🟢)2.006s (-46.6% 🟢)0.806s451.16x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.272s (-68.9% 🟢)14.115s (-65.8% 🟢)1.842s71.00x
▲ VercelNext.js (Turbopack)13.606s (-72.7% 🟢)15.509s (-70.0% 🟢)1.903s61.11x
▲ VercelExpress13.810s (-60.0% 🟢)16.683s (-54.7% 🟢)2.872s61.13x

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

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.047s (-48.7% 🟢)2.506s (-42.6% 🟢)0.459s481.00x
🐘 PostgresNitro2.132s (-48.0% 🟢)2.798s (-39.2% 🟢)0.666s431.04x
💻 LocalExpress2.603s (-71.7% 🟢)3.007s (-70.0% 🟢)0.404s401.27x
💻 LocalNitro2.699s (-71.0% 🟢)3.008s (-70.0% 🟢)0.308s401.32x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro39.315s (-59.4% 🟢)41.156s (-58.2% 🟢)1.841s31.00x
▲ VercelExpress40.781s (-68.6% 🟢)43.325s (-67.2% 🟢)2.544s31.04x
▲ VercelNext.js (Turbopack)41.245s (-61.5% 🟢)42.926s (-60.6% 🟢)1.681s31.05x

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

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.165s (-41.6% 🟢)1.006s (~)0.841s601.00x
🐘 PostgresNitro0.172s (-39.4% 🟢)1.006s (~)0.834s601.04x
💻 LocalExpress0.421s (-24.9% 🟢)1.004s (~)0.583s602.55x
💻 LocalNitro0.463s (-23.5% 🟢)1.004s (-1.7%)0.541s602.81x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.161s (+10.6% 🔺)4.409s (+21.2% 🔺)2.249s141.00x
▲ VercelNext.js (Turbopack)2.599s (+28.5% 🔺)4.308s (+13.6% 🔺)1.709s141.20x
▲ VercelNitro2.656s (+59.9% 🔺)4.513s (+34.7% 🔺)1.858s141.23x

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

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.285s (-44.0% 🟢)1.006s (~)0.720s901.00x
🐘 PostgresNitro0.298s (-40.0% 🟢)1.006s (~)0.708s901.04x
💻 LocalExpress2.156s (-14.2% 🟢)2.685s (-10.8% 🟢)0.529s347.56x
💻 LocalNitro2.179s (-14.2% 🟢)2.912s (-3.2%)0.733s317.64x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)8.136s (+130.1% 🔺)10.219s (+96.8% 🔺)2.083s91.00x
▲ VercelExpress8.249s (+170.8% 🔺)10.683s (+122.2% 🔺)2.434s91.01x
▲ VercelNitro9.696s (+200.6% 🔺)11.640s (+141.4% 🔺)1.944s81.19x

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

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.558s (-31.8% 🟢)1.005s (-1.2%)0.447s1201.00x
🐘 PostgresNitro0.652s (-17.5% 🟢)1.006s (~)0.354s1201.17x
💻 LocalExpress9.153s (-18.2% 🟢)9.795s (-18.0% 🟢)0.643s1316.40x
💻 LocalNitro10.117s (-9.6% 🟢)10.778s (-7.6% 🟢)0.661s1218.13x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.676s (+119.6% 🔺)24.944s (+103.0% 🔺)2.268s51.00x
▲ VercelNitro23.736s (+207.4% 🔺)25.097s (+167.0% 🔺)1.361s51.05x
▲ VercelExpress28.260s (+280.9% 🔺)30.473s (+229.6% 🔺)2.213s41.25x

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

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.126s (+465.4% 🔺)2.004s (+99.5% 🔺)0.010s (-19.8% 🟢)2.016s (+98.0% 🔺)0.890s101.00x
🐘 PostgresExpress1.133s (+452.5% 🔺)2.002s (+100.5% 🔺)0.001s (-25.0% 🟢)2.010s (+98.8% 🔺)0.877s101.01x
💻 LocalNitro1.143s (+434.8% 🔺)2.005s (+99.6% 🔺)0.012s (-2.4%)2.019s (+98.2% 🔺)0.877s101.02x
🐘 PostgresNitro1.157s (+464.4% 🔺)1.999s (+100.0% 🔺)0.001s (-20.0% 🟢)2.010s (+98.8% 🔺)0.853s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)2.185s (-68.1% 🟢)3.165s (-63.4% 🟢)1.925s (+204.7% 🔺)5.496s (-43.9% 🟢)3.311s101.00x
▲ VercelNitro2.295s (-40.1% 🟢)3.603s (-31.7% 🟢)13.820s (+1762.3% 🔺)17.886s (+175.9% 🔺)15.591s101.05x
▲ VercelExpress2.329s (-7.0% 🟢)3.832s (-6.3% 🟢)1.518s (+58.0% 🔺)5.905s (+5.6% 🔺)3.576s101.07x

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

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.490s (+136.5% 🔺)2.005s (+99.2% 🔺)0.003s (-12.1% 🟢)2.022s (+97.7% 🔺)0.532s301.00x
🐘 PostgresNitro1.528s (+144.8% 🔺)2.003s (+98.9% 🔺)0.004s (-3.3%)2.027s (+98.2% 🔺)0.499s301.03x
💻 LocalNitro1.707s (+103.5% 🔺)2.011s (+98.7% 🔺)0.010s (+6.7% 🔺)2.202s (+97.3% 🔺)0.495s281.15x
💻 LocalExpress1.877s (+148.0% 🔺)2.011s (+95.5% 🔺)0.009s (-0.9%)2.394s (+130.2% 🔺)0.517s271.26x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.935s (-8.8% 🟢)7.705s (-3.8%)0.216s (-47.0% 🟢)8.447s (-4.4%)2.512s81.00x
▲ VercelNitro5.975s (-79.7% 🟢)7.548s (-75.5% 🟢)0.263s (+134.7% 🔺)8.277s (-74.0% 🟢)2.302s81.01x
▲ VercelNext.js (Turbopack)6.750s (-60.1% 🟢)7.706s (-57.8% 🟢)0.297s (+40.7% 🔺)8.648s (-54.3% 🟢)1.898s71.14x

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

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.637s (-34.3% 🟢)1.034s (-17.1% 🟢)0.000s (-58.6% 🟢)1.042s (-17.2% 🟢)0.405s581.00x
🐘 PostgresExpress0.647s (-32.7% 🟢)1.050s (-17.8% 🟢)0.000s (+58.6% 🔺)1.060s (-18.8% 🟢)0.413s581.02x
💻 LocalExpress1.339s (+9.3% 🔺)2.016s (~)0.001s (+70.0% 🔺)2.018s (~)0.679s302.10x
💻 LocalNitro1.726s (+41.2% 🔺)2.015s (~)0.000s (+140.0% 🔺)2.418s (+19.6% 🔺)0.691s252.71x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)3.534s (-65.3% 🟢)4.591s (-60.1% 🟢)0.000s (NaN%)5.094s (-57.7% 🟢)1.560s121.00x
▲ VercelNitro3.538s (+16.0% 🔺)4.997s (+13.8% 🔺)0.000s (+225.0% 🔺)5.453s (+13.4% 🔺)1.916s121.00x
▲ VercelExpress3.646s (-2.5%)5.083s (~)0.000s (~)5.696s (+3.0%)2.050s111.03x

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

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.294s (-27.0% 🟢)1.905s (-12.5% 🟢)0.000s (NaN%)1.924s (-12.5% 🟢)0.630s321.00x
🐘 PostgresNitro1.320s (-26.3% 🟢)2.031s (-5.1% 🟢)0.000s (-100.0% 🟢)2.043s (-6.0% 🟢)0.723s301.02x
💻 LocalExpress3.031s (-12.6% 🟢)3.777s (-6.4% 🟢)0.001s (-21.9% 🟢)3.780s (-6.3% 🟢)0.749s162.34x
💻 LocalNitro3.076s (-9.2% 🟢)3.777s (-6.3% 🟢)0.001s (-6.2% 🟢)3.779s (-6.4% 🟢)0.703s162.38x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.447s (+33.1% 🔺)7.047s (+31.1% 🔺)0.000s (-54.2% 🟢)7.517s (+29.7% 🔺)2.070s81.00x
▲ VercelNext.js (Turbopack)5.517s (-1.8%)7.043s (+0.9%)0.000s (~)7.537s (~)2.020s81.01x
▲ VercelExpress6.217s (+35.5% 🔺)7.655s (+27.1% 🔺)0.000s (NaN%)8.501s (+31.7% 🔺)2.284s81.14x

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

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress17/21
🐘 PostgresExpress18/21
▲ VercelNitro9/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)▲ Vercel21/21
Nitro🐘 Postgres15/21
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)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production120002191419
✅ 💻 Local Development158702191806
✅ 📦 Local Production158702191806
✅ 🐘 Local Postgres158702191806
✅ 🪟 Windows12900129
✅ 📋 Other7270176903
Total6817010527869

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro103026
✅ example103026
✅ express103026
✅ fastify103026
✅ hono103026
✅ nextjs-turbopack12702
✅ nextjs-webpack12702
✅ nitro103026
✅ nuxt103026
✅ sveltekit12207
✅ vite103026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack12900
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable104025
✅ e2e-local-dev-tanstack-start-104025
✅ e2e-local-postgres-nest-stable104025
✅ e2e-local-postgres-tanstack-start-104025
✅ e2e-local-prod-nest-stable104025
✅ e2e-local-prod-tanstack-start-104025
✅ e2e-vercel-prod-tanstack-start103026

📋 View full workflow run

Comment threadpackages/nitro/src/index.ts Outdated
@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably know better than me whether the nits apply, so use your judgement there. Happy to approve this once the e2e tests are running again and CI is green

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: blocking issues found

Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.test.ts
Comment threadpackages/builders/src/types.ts Outdated
* Extra esbuild options merged into every bundle this builder produces
* (steps, intermediate workflow, final workflow wrapper, webhook).
*/
esbuildOptions?: Partial<BuildOptions>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

esbuildOptions: Partial<BuildOptions> is a broad surface for one knob

The new field accepts the full esbuild BuildOptions and is spread into four bundle calls with very different needs (steps: node/esm, workflow VM: neutral/cjs, webhook: node/esm, final wrapper: node/esm). The current spread order puts user options first so builder-required options like platform, format, bundle, plugins win — that's defensive — but the API still implies "you can pass any esbuild option" when in practice almost everything that matters is overridden.

Given the only consumer is sourcesContent: false, consider either narrowing the public type to a small allowlist or naming the field something closer to its actual purpose. Not blocking, but the surface area will be hard to take back later.

@VaguelySerious

Copy link
Copy Markdown
Member

Seems like tests are still failing, please re-ping me when they pass 👋

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

Hey @VaguelySerious, the related tests are passing 🥳

@vercel

vercelBot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Deployment failed with the following error:

The Preview Deployment Suffix domain "labs.vercel.dev" is not available under the current scope. Learn more: https://vercel.link/3a1

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: no blocking issues

Comment threadpackages/nitro/src/index.ts Outdated
};

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The webhook handler is registered at /.well-known/workflow/v1/webhook/:token (line 214), but this functionRules key uses webhook/**. I built workbench/nitro-v3 with NITRO_PRESET=vercel and workflow.runtime: 'nodejs22.x' and verified that nitro's vercel preset generates two function dirs in this case:

  • webhook/[token].func — created via routeFuncRouter.match against the :token route, gets the runtime override.
  • webhook/[...].func — created from this ** rule, also gets the runtime override.

In config.json, the webhook/(?:.*) route emitted by this rule comes before webhook/(?<token>[^/]+), so [token].func ends up unreachable. The result is a wasted function dir (extra disk/build/deploy size) and slightly looser routing (/webhook/ with no token now matches).

Suggested fix: use the same pattern as the handler, e.g. '/.well-known/workflow/v1/webhook/:token'. With matching keys, nitro reuses the single funcDir under the runtime override.

// V2 combined: a single trigger covers both `__wkf_workflow_*`
// (workflow orchestration) and `__wkf_step_*` (step execution),
// since the same handler dispatches both.
experimentalTriggers: [WORKFLOW_QUEUE_TRIGGER],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The comment two lines above says "merged with any user-defined rules at the same paths so explicit user config (e.g. memory) is preserved", but the spread order here (user first, then maxDuration / experimentalTriggers) means workflow's values always win. Only fields that workflow doesn't touch (like memory) survive — user-set maxDuration, runtime, or additional experimentalTriggers get clobbered.

Two small things:

  1. The comment should match the behavior — something like "workflow's required triggers/maxDuration take precedence; unrelated fields like memory are preserved."
  2. Other integrations (e.g. packages/sveltekit) concatenate user triggers rather than replacing them. Probably not an issue in practice since this is a workflow-owned route, but worth deciding deliberately.

Also (related): also exercised by the E2E Vercel Prod Tests (hono) job, which currently fails on maxRetries=0 disables retries (60s timeout) on this PR while passing on main. And E2E Local Prod Tests (tanstack-start) fails on abortVoidSleepTimeoutWorkflow (also passes on main). Both look like potential regressions worth investigating before merge — they aren't in the known-flaky set.

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';
rules[webhookPath] = { ...rules[webhookPath], runtime };
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

runtime is propagated to the flow and webhook routes but not to the manifest route (/.well-known/workflow/v1/manifest.json, registered via addManifestHandler). If a user picks a non-default runtime, the manifest gets served from the catch-all __server function on the default runtime. Low impact (manifest is static JSON), but inconsistent — worth either documenting why it's intentionally excluded, or applying the same override.

return majorVersion === 2;
}
return !nitro.routing;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

This PR adds non-trivial new logic (isNitroV2, the useLegacyVercelBuild branch, the functionRules merge) with no unit test coverage.

Also heads-up: the existing packages/nitro/src/index.test.ts is already broken on main (2 of 10 tests reference #workflow/steps.mjs, which no longer exists since the V1→V2 combined bundle change). Not introduced by this PR, but since the file is already touching the same area, fixing those tests and adding a couple of cases for isNitroV2 and the v3-vercel branch would be a low-cost addition.

Comment threadpackages/nitro/src/builders.ts Outdated
*/
function getNitroStringExternals(nitro: Nitro): string[] | undefined {
const externals = nitro.options.externals?.external?.filter(
const external = (nitro.options as { externals?: { external?: unknown[] } })

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

The structural cast widens external to unknown[] then filters strings — works, but a named alias would read better:

typeNitroV2Options={externals?: {external?: unknown[]}};constexternal=(nitro.optionsasNitroV2Options).externals?.external;

Non-blocking.

Comment threadworkbench/vite/vite.config.ts
The functionRule was keyed by `webhook/**` while the actual handler is
registered at `webhook/:token`, so nitro v3's vercel preset created a
second `webhook/[...].func` next to the real one and shadowed it in the
generated routes. Use the same `:token` pattern as the handler so the
runtime override applies to the function that actually serves traffic.
Also propagate `workflow.runtime` to the public manifest route for
consistency, clarify the merge-precedence comment, and refresh the unit
tests to cover the v3 functionRules path and the `isNitroV2` detection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Wielander <peter.wielander@vercel.com>
@VaguelySerious

Copy link
Copy Markdown
Member

auto-merge is on, waiting for CI to pass

Comment threadpnpm-workspace.yaml
enhanced-resolve: 5.19.0
esbuild: ^0.27.3
nitro: 3.0.1-alpha.1
nitro: ^3.0.260429-beta

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should nitro on the workbench not be a stable release? 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro v2 is nitropack and Nitro v3 is nitro so I think it should be fine. Most the things target Nitro v3 although it's not got a "stable" release yet till Nuxt v5.

@github-actionsgithub-actionsBot mentioned this pull request May 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for c1242e8 (AI decision).

This commit refactors @workflow/nitro to use Nitro v3's functionRules for workflow routes, a code path that does not exist on stable (stable still uses the legacy VercelBuilder for all Vercel deploys regardless of Nitro version). It also bumps the nitro catalog dependency from 3.0.1-alpha.1 to ^3.0.260429-beta, which is a substantial upstream change tied to features only used on main. The webhook pattern fix called out in the changeset only applies to the v3 functionRules path that isn't present on stable.

To override, re-run the Backport to stable workflow manually via workflow_dispatch with this commit SHA.

Sign up for freeto 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.

3 participants

@RihanArfan@VaguelySerious@pi0
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor(nitro): use nitro v3 functionRules workflow routes - #1575

Merged
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement
May 20, 2026
Merged

refactor(nitro): use nitro v3 functionRules workflow routes#1575
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement

Conversation

@RihanArfan

@RihanArfanRihanArfan commented Apr 1, 2026

Copy link
Copy Markdown
Member

Description

Currently we separately build the Workflows functions for the Vercel Build Output API when deploying a Nitro app to Vercel. This means the Workflows routes aren't part of the Nitro bundle.

This PR changes to now handle the Workflows routes as handlers within Nitro v3. Nitro v2 is untouched.

How did you test your changes?

Locally ran nitro dev in the nitro-v3 workspace and visited the API route POST /api/test-direct-step-call.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR
    • During beta, we only use "patch" mode for changes. Don't tag minor/major versions.
    • Use pnpm changeset --empty if you are changing documentation or workbench apps
  • 🔒 DCO sign-off passes (run git commit --signoff on your commits)
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete

@changeset-bot

changeset-botBot commented Apr 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2c843be

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

This PR includes changesets to release 17 packages
NameType
@workflow/nitroPatch
@workflow/nuxtPatch
workflowPatch
@workflow/aiPatch
@workflow/world-testingPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Comment threadpackages/nitro/src/index.ts
@RihanArfan
RihanArfanforce-pushed the feat/nitro-builder-improvement branch from e312e97 to 3eb081aCompareApril 7, 2026 10:28
@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.030s (-29.7% 🟢)1.006s (~)0.975s101.00x
💻 LocalExpress0.031s (-29.6% 🟢)1.005s (~)0.974s101.03x
🐘 PostgresExpress0.043s (-25.5% 🟢)1.010s (~)0.967s101.43x
🐘 PostgresNitro0.048s (-49.2% 🟢)1.010s (-3.1%)0.962s101.60x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.224s (-10.9% 🟢)1.890s (-19.0% 🟢)1.666s101.00x
▲ VercelNitro0.225s (-45.0% 🟢)2.732s (+8.9% 🔺)2.507s101.01x
▲ VercelExpress0.397s (+68.6% 🔺)2.027s (-5.1% 🟢)1.630s101.77x

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

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.072s (-5.3% 🟢)2.007s (~)0.935s101.00x
💻 LocalExpress1.075s (-4.5%)2.006s (~)0.931s101.00x
🐘 PostgresExpress1.082s (-5.6% 🟢)2.009s (~)0.927s101.01x
🐘 PostgresNitro1.083s (-5.0%)2.010s (~)0.927s101.01x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.539s (-17.9% 🟢)3.295s (-13.5% 🟢)1.756s101.00x
▲ VercelNext.js (Turbopack)1.689s (-17.0% 🟢)4.668s (+21.9% 🔺)2.979s101.10x
▲ VercelNitro2.027s (-47.9% 🟢)3.895s (-34.1% 🟢)1.869s101.32x

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

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.412s (-4.7%)11.021s (~)0.608s31.00x
🐘 PostgresExpress10.416s (-5.0%)11.016s (~)0.600s31.00x
💻 LocalNitro10.424s (-4.8%)11.021s (~)0.598s31.00x
🐘 PostgresNitro10.433s (-4.0%)11.020s (~)0.586s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)13.515s (-22.0% 🟢)15.908s (-18.0% 🟢)2.392s21.00x
▲ VercelExpress13.568s (-20.1% 🟢)15.557s (-22.3% 🟢)1.989s21.00x
▲ VercelNitro13.587s (-42.7% 🟢)15.559s (-38.1% 🟢)1.972s21.01x

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

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.456s (-7.7% 🟢)14.017s (-6.7% 🟢)0.561s51.00x
💻 LocalExpress13.459s (-10.1% 🟢)14.025s (-6.7% 🟢)0.566s51.00x
🐘 PostgresNitro13.466s (-7.7% 🟢)14.019s (-6.7% 🟢)0.553s51.00x
💻 LocalNitro13.479s (-10.5% 🟢)14.027s (-12.5% 🟢)0.548s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express20.282s (-59.7% 🟢)22.320s (-57.5% 🟢)2.038s31.00x
▲ VercelNext.js (Turbopack)20.648s (-60.7% 🟢)22.415s (-59.0% 🟢)1.768s31.02x
▲ VercelNitro20.776s (-67.8% 🟢)22.345s (-66.5% 🟢)1.569s31.02x

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

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express11.896s (-28.3% 🟢)12.020s (-29.4% 🟢)0.124s81.00x
💻 LocalNitro11.919s (-29.0% 🟢)12.022s (-29.4% 🟢)0.103s81.00x
🐘 PostgresExpress11.979s (-14.5% 🟢)12.268s (-15.9% 🟢)0.288s81.01x
🐘 PostgresNitro12.158s (-12.9% 🟢)12.872s (-10.0% 🟢)0.714s71.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express30.115s (-75.2% 🟢)32.130s (-74.0% 🟢)2.014s31.00x
▲ VercelNitro30.529s (-92.8% 🟢)32.167s (-92.4% 🟢)1.638s31.01x
▲ VercelNext.js (Turbopack)31.252s (-92.1% 🟢)32.624s (-91.7% 🟢)1.372s31.04x

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

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.138s (-9.7% 🟢)2.007s (~)0.870s151.00x
💻 LocalExpress1.166s (-21.7% 🟢)2.006s (~)0.840s151.02x
💻 LocalNitro1.175s (-28.0% 🟢)2.006s (-3.3%)0.830s151.03x
🐘 PostgresNitro1.238s (-2.9%)2.074s (+3.2%)0.836s151.09x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.605s (-7.5% 🟢)3.989s (-7.7% 🟢)1.383s91.00x
▲ VercelExpress2.632s (-8.0% 🟢)4.521s (-2.2%)1.889s71.01x
▲ VercelNext.js (Turbopack)2.660s (-21.7% 🟢)4.213s (-14.6% 🟢)1.553s81.02x

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

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.199s (-49.2% 🟢)2.006s (-33.3% 🟢)0.807s151.00x
🐘 PostgresNitro1.234s (-47.5% 🟢)2.008s (-33.3% 🟢)0.773s151.03x
💻 LocalExpress1.611s (-45.5% 🟢)2.006s (-41.9% 🟢)0.395s151.34x
💻 LocalNitro1.781s (-43.3% 🟢)2.005s (-48.4% 🟢)0.224s151.49x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.495s (-3.4%)5.288s (+3.5%)1.793s61.00x
▲ VercelNext.js (Turbopack)3.647s (-48.6% 🟢)5.434s (-39.0% 🟢)1.787s61.04x
▲ VercelNitro4.003s (-1.2%)5.826s (-1.6%)1.823s61.15x

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

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.290s (-63.0% 🟢)2.007s (-50.0% 🟢)0.717s151.00x
🐘 PostgresNitro1.350s (-61.2% 🟢)2.008s (-49.9% 🟢)0.659s151.05x
💻 LocalExpress4.222s (-49.4% 🟢)4.582s (-49.2% 🟢)0.361s73.27x
💻 LocalNitro5.125s (-38.6% 🟢)5.678s (-37.1% 🟢)0.554s63.97x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.678s (+32.7% 🔺)6.756s (+22.1% 🔺)2.078s51.00x
▲ VercelExpress6.267s (+47.8% 🔺)8.267s (+34.9% 🔺)2.000s41.34x
▲ VercelNext.js (Turbopack)7.564s (-15.2% 🟢)9.835s (-10.3% 🟢)2.271s41.62x

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

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.145s (-8.9% 🟢)2.008s (~)0.863s151.00x
🐘 PostgresNitro1.149s (-8.6% 🟢)2.008s (~)0.859s151.00x
💻 LocalExpress1.361s (-28.1% 🟢)2.006s (-15.1% 🟢)0.645s151.19x
💻 LocalNitro1.371s (-26.5% 🟢)2.006s (-14.3% 🟢)0.635s151.20x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.498s (+1.6%)4.099s (-1.7%)1.601s81.00x
▲ VercelNext.js (Turbopack)2.715s (-7.4% 🟢)4.421s (-4.8%)1.707s71.09x
▲ VercelExpress2.897s (+12.2% 🔺)4.519s (+3.9%)1.622s71.16x

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

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.192s (-49.1% 🟢)2.008s (-33.3% 🟢)0.817s151.00x
🐘 PostgresExpress1.198s (-48.8% 🟢)2.009s (-33.3% 🟢)0.811s151.01x
💻 LocalExpress1.702s (-45.7% 🟢)2.008s (-46.6% 🟢)0.306s151.43x
💻 LocalNitro2.025s (-33.9% 🟢)2.391s (-38.5% 🟢)0.366s131.70x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.357s (+3.8%)5.248s (+3.4%)1.892s61.00x
▲ VercelExpress3.634s (+13.8% 🔺)5.286s (+10.3% 🔺)1.652s61.08x
▲ VercelNext.js (Turbopack)4.356s (+38.6% 🔺)6.224s (+37.6% 🔺)1.868s61.30x

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

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.271s (-63.7% 🟢)2.007s (-50.0% 🟢)0.735s151.00x
🐘 PostgresNitro1.304s (-62.5% 🟢)2.007s (-49.9% 🟢)0.703s151.03x
💻 LocalExpress4.961s (-43.6% 🟢)5.514s (-40.5% 🟢)0.553s63.90x
💻 LocalNitro6.189s (-32.3% 🟢)6.415s (-36.0% 🟢)0.226s54.87x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.914s (-3.5%)6.839s (~)1.925s51.00x
▲ VercelExpress5.278s (-17.7% 🟢)7.562s (-7.5% 🟢)2.284s41.07x
▲ VercelNext.js (Turbopack)6.788s (~)8.497s (-0.5%)1.709s41.38x

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

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.434s (-48.2% 🟢)1.006s (-1.6%)0.572s601.00x
🐘 PostgresNitro0.454s (-44.7% 🟢)1.024s (+1.8%)0.570s591.05x
💻 LocalNitro0.485s (-50.5% 🟢)1.004s (-8.2% 🟢)0.519s601.12x
💻 LocalExpress0.563s (-42.7% 🟢)1.092s (+1.5%)0.528s571.30x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.763s (-78.4% 🟢)6.470s (-73.1% 🟢)1.707s101.00x
▲ VercelNext.js (Turbopack)5.279s (-63.6% 🟢)6.979s (-56.6% 🟢)1.700s91.11x
▲ VercelExpress5.386s (-71.7% 🟢)7.590s (-64.4% 🟢)2.204s81.13x

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

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.038s (-46.1% 🟢)1.791s (-14.7% 🟢)0.753s511.00x
🐘 PostgresExpress1.094s (-44.7% 🟢)1.705s (-24.5% 🟢)0.611s531.05x
💻 LocalExpress1.163s (-61.4% 🟢)2.005s (-44.1% 🟢)0.842s451.12x
💻 LocalNitro1.200s (-60.5% 🟢)2.006s (-46.6% 🟢)0.806s451.16x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.272s (-68.9% 🟢)14.115s (-65.8% 🟢)1.842s71.00x
▲ VercelNext.js (Turbopack)13.606s (-72.7% 🟢)15.509s (-70.0% 🟢)1.903s61.11x
▲ VercelExpress13.810s (-60.0% 🟢)16.683s (-54.7% 🟢)2.872s61.13x

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

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.047s (-48.7% 🟢)2.506s (-42.6% 🟢)0.459s481.00x
🐘 PostgresNitro2.132s (-48.0% 🟢)2.798s (-39.2% 🟢)0.666s431.04x
💻 LocalExpress2.603s (-71.7% 🟢)3.007s (-70.0% 🟢)0.404s401.27x
💻 LocalNitro2.699s (-71.0% 🟢)3.008s (-70.0% 🟢)0.308s401.32x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro39.315s (-59.4% 🟢)41.156s (-58.2% 🟢)1.841s31.00x
▲ VercelExpress40.781s (-68.6% 🟢)43.325s (-67.2% 🟢)2.544s31.04x
▲ VercelNext.js (Turbopack)41.245s (-61.5% 🟢)42.926s (-60.6% 🟢)1.681s31.05x

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

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.165s (-41.6% 🟢)1.006s (~)0.841s601.00x
🐘 PostgresNitro0.172s (-39.4% 🟢)1.006s (~)0.834s601.04x
💻 LocalExpress0.421s (-24.9% 🟢)1.004s (~)0.583s602.55x
💻 LocalNitro0.463s (-23.5% 🟢)1.004s (-1.7%)0.541s602.81x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.161s (+10.6% 🔺)4.409s (+21.2% 🔺)2.249s141.00x
▲ VercelNext.js (Turbopack)2.599s (+28.5% 🔺)4.308s (+13.6% 🔺)1.709s141.20x
▲ VercelNitro2.656s (+59.9% 🔺)4.513s (+34.7% 🔺)1.858s141.23x

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

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.285s (-44.0% 🟢)1.006s (~)0.720s901.00x
🐘 PostgresNitro0.298s (-40.0% 🟢)1.006s (~)0.708s901.04x
💻 LocalExpress2.156s (-14.2% 🟢)2.685s (-10.8% 🟢)0.529s347.56x
💻 LocalNitro2.179s (-14.2% 🟢)2.912s (-3.2%)0.733s317.64x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)8.136s (+130.1% 🔺)10.219s (+96.8% 🔺)2.083s91.00x
▲ VercelExpress8.249s (+170.8% 🔺)10.683s (+122.2% 🔺)2.434s91.01x
▲ VercelNitro9.696s (+200.6% 🔺)11.640s (+141.4% 🔺)1.944s81.19x

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

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.558s (-31.8% 🟢)1.005s (-1.2%)0.447s1201.00x
🐘 PostgresNitro0.652s (-17.5% 🟢)1.006s (~)0.354s1201.17x
💻 LocalExpress9.153s (-18.2% 🟢)9.795s (-18.0% 🟢)0.643s1316.40x
💻 LocalNitro10.117s (-9.6% 🟢)10.778s (-7.6% 🟢)0.661s1218.13x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.676s (+119.6% 🔺)24.944s (+103.0% 🔺)2.268s51.00x
▲ VercelNitro23.736s (+207.4% 🔺)25.097s (+167.0% 🔺)1.361s51.05x
▲ VercelExpress28.260s (+280.9% 🔺)30.473s (+229.6% 🔺)2.213s41.25x

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

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.126s (+465.4% 🔺)2.004s (+99.5% 🔺)0.010s (-19.8% 🟢)2.016s (+98.0% 🔺)0.890s101.00x
🐘 PostgresExpress1.133s (+452.5% 🔺)2.002s (+100.5% 🔺)0.001s (-25.0% 🟢)2.010s (+98.8% 🔺)0.877s101.01x
💻 LocalNitro1.143s (+434.8% 🔺)2.005s (+99.6% 🔺)0.012s (-2.4%)2.019s (+98.2% 🔺)0.877s101.02x
🐘 PostgresNitro1.157s (+464.4% 🔺)1.999s (+100.0% 🔺)0.001s (-20.0% 🟢)2.010s (+98.8% 🔺)0.853s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)2.185s (-68.1% 🟢)3.165s (-63.4% 🟢)1.925s (+204.7% 🔺)5.496s (-43.9% 🟢)3.311s101.00x
▲ VercelNitro2.295s (-40.1% 🟢)3.603s (-31.7% 🟢)13.820s (+1762.3% 🔺)17.886s (+175.9% 🔺)15.591s101.05x
▲ VercelExpress2.329s (-7.0% 🟢)3.832s (-6.3% 🟢)1.518s (+58.0% 🔺)5.905s (+5.6% 🔺)3.576s101.07x

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

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.490s (+136.5% 🔺)2.005s (+99.2% 🔺)0.003s (-12.1% 🟢)2.022s (+97.7% 🔺)0.532s301.00x
🐘 PostgresNitro1.528s (+144.8% 🔺)2.003s (+98.9% 🔺)0.004s (-3.3%)2.027s (+98.2% 🔺)0.499s301.03x
💻 LocalNitro1.707s (+103.5% 🔺)2.011s (+98.7% 🔺)0.010s (+6.7% 🔺)2.202s (+97.3% 🔺)0.495s281.15x
💻 LocalExpress1.877s (+148.0% 🔺)2.011s (+95.5% 🔺)0.009s (-0.9%)2.394s (+130.2% 🔺)0.517s271.26x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.935s (-8.8% 🟢)7.705s (-3.8%)0.216s (-47.0% 🟢)8.447s (-4.4%)2.512s81.00x
▲ VercelNitro5.975s (-79.7% 🟢)7.548s (-75.5% 🟢)0.263s (+134.7% 🔺)8.277s (-74.0% 🟢)2.302s81.01x
▲ VercelNext.js (Turbopack)6.750s (-60.1% 🟢)7.706s (-57.8% 🟢)0.297s (+40.7% 🔺)8.648s (-54.3% 🟢)1.898s71.14x

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

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.637s (-34.3% 🟢)1.034s (-17.1% 🟢)0.000s (-58.6% 🟢)1.042s (-17.2% 🟢)0.405s581.00x
🐘 PostgresExpress0.647s (-32.7% 🟢)1.050s (-17.8% 🟢)0.000s (+58.6% 🔺)1.060s (-18.8% 🟢)0.413s581.02x
💻 LocalExpress1.339s (+9.3% 🔺)2.016s (~)0.001s (+70.0% 🔺)2.018s (~)0.679s302.10x
💻 LocalNitro1.726s (+41.2% 🔺)2.015s (~)0.000s (+140.0% 🔺)2.418s (+19.6% 🔺)0.691s252.71x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)3.534s (-65.3% 🟢)4.591s (-60.1% 🟢)0.000s (NaN%)5.094s (-57.7% 🟢)1.560s121.00x
▲ VercelNitro3.538s (+16.0% 🔺)4.997s (+13.8% 🔺)0.000s (+225.0% 🔺)5.453s (+13.4% 🔺)1.916s121.00x
▲ VercelExpress3.646s (-2.5%)5.083s (~)0.000s (~)5.696s (+3.0%)2.050s111.03x

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

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.294s (-27.0% 🟢)1.905s (-12.5% 🟢)0.000s (NaN%)1.924s (-12.5% 🟢)0.630s321.00x
🐘 PostgresNitro1.320s (-26.3% 🟢)2.031s (-5.1% 🟢)0.000s (-100.0% 🟢)2.043s (-6.0% 🟢)0.723s301.02x
💻 LocalExpress3.031s (-12.6% 🟢)3.777s (-6.4% 🟢)0.001s (-21.9% 🟢)3.780s (-6.3% 🟢)0.749s162.34x
💻 LocalNitro3.076s (-9.2% 🟢)3.777s (-6.3% 🟢)0.001s (-6.2% 🟢)3.779s (-6.4% 🟢)0.703s162.38x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.447s (+33.1% 🔺)7.047s (+31.1% 🔺)0.000s (-54.2% 🟢)7.517s (+29.7% 🔺)2.070s81.00x
▲ VercelNext.js (Turbopack)5.517s (-1.8%)7.043s (+0.9%)0.000s (~)7.537s (~)2.020s81.01x
▲ VercelExpress6.217s (+35.5% 🔺)7.655s (+27.1% 🔺)0.000s (NaN%)8.501s (+31.7% 🔺)2.284s81.14x

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

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress17/21
🐘 PostgresExpress18/21
▲ VercelNitro9/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)▲ Vercel21/21
Nitro🐘 Postgres15/21
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)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production120002191419
✅ 💻 Local Development158702191806
✅ 📦 Local Production158702191806
✅ 🐘 Local Postgres158702191806
✅ 🪟 Windows12900129
✅ 📋 Other7270176903
Total6817010527869

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro103026
✅ example103026
✅ express103026
✅ fastify103026
✅ hono103026
✅ nextjs-turbopack12702
✅ nextjs-webpack12702
✅ nitro103026
✅ nuxt103026
✅ sveltekit12207
✅ vite103026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack12900
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable104025
✅ e2e-local-dev-tanstack-start-104025
✅ e2e-local-postgres-nest-stable104025
✅ e2e-local-postgres-tanstack-start-104025
✅ e2e-local-prod-nest-stable104025
✅ e2e-local-prod-tanstack-start-104025
✅ e2e-vercel-prod-tanstack-start103026

📋 View full workflow run

Comment threadpackages/nitro/src/index.ts Outdated
@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably know better than me whether the nits apply, so use your judgement there. Happy to approve this once the e2e tests are running again and CI is green

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: blocking issues found

Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.test.ts
Comment threadpackages/builders/src/types.ts Outdated
* Extra esbuild options merged into every bundle this builder produces
* (steps, intermediate workflow, final workflow wrapper, webhook).
*/
esbuildOptions?: Partial<BuildOptions>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

esbuildOptions: Partial<BuildOptions> is a broad surface for one knob

The new field accepts the full esbuild BuildOptions and is spread into four bundle calls with very different needs (steps: node/esm, workflow VM: neutral/cjs, webhook: node/esm, final wrapper: node/esm). The current spread order puts user options first so builder-required options like platform, format, bundle, plugins win — that's defensive — but the API still implies "you can pass any esbuild option" when in practice almost everything that matters is overridden.

Given the only consumer is sourcesContent: false, consider either narrowing the public type to a small allowlist or naming the field something closer to its actual purpose. Not blocking, but the surface area will be hard to take back later.

@VaguelySerious

Copy link
Copy Markdown
Member

Seems like tests are still failing, please re-ping me when they pass 👋

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

Hey @VaguelySerious, the related tests are passing 🥳

@vercel

vercelBot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Deployment failed with the following error:

The Preview Deployment Suffix domain "labs.vercel.dev" is not available under the current scope. Learn more: https://vercel.link/3a1

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: no blocking issues

Comment threadpackages/nitro/src/index.ts Outdated
};

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The webhook handler is registered at /.well-known/workflow/v1/webhook/:token (line 214), but this functionRules key uses webhook/**. I built workbench/nitro-v3 with NITRO_PRESET=vercel and workflow.runtime: 'nodejs22.x' and verified that nitro's vercel preset generates two function dirs in this case:

  • webhook/[token].func — created via routeFuncRouter.match against the :token route, gets the runtime override.
  • webhook/[...].func — created from this ** rule, also gets the runtime override.

In config.json, the webhook/(?:.*) route emitted by this rule comes before webhook/(?<token>[^/]+), so [token].func ends up unreachable. The result is a wasted function dir (extra disk/build/deploy size) and slightly looser routing (/webhook/ with no token now matches).

Suggested fix: use the same pattern as the handler, e.g. '/.well-known/workflow/v1/webhook/:token'. With matching keys, nitro reuses the single funcDir under the runtime override.

// V2 combined: a single trigger covers both `__wkf_workflow_*`
// (workflow orchestration) and `__wkf_step_*` (step execution),
// since the same handler dispatches both.
experimentalTriggers: [WORKFLOW_QUEUE_TRIGGER],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The comment two lines above says "merged with any user-defined rules at the same paths so explicit user config (e.g. memory) is preserved", but the spread order here (user first, then maxDuration / experimentalTriggers) means workflow's values always win. Only fields that workflow doesn't touch (like memory) survive — user-set maxDuration, runtime, or additional experimentalTriggers get clobbered.

Two small things:

  1. The comment should match the behavior — something like "workflow's required triggers/maxDuration take precedence; unrelated fields like memory are preserved."
  2. Other integrations (e.g. packages/sveltekit) concatenate user triggers rather than replacing them. Probably not an issue in practice since this is a workflow-owned route, but worth deciding deliberately.

Also (related): also exercised by the E2E Vercel Prod Tests (hono) job, which currently fails on maxRetries=0 disables retries (60s timeout) on this PR while passing on main. And E2E Local Prod Tests (tanstack-start) fails on abortVoidSleepTimeoutWorkflow (also passes on main). Both look like potential regressions worth investigating before merge — they aren't in the known-flaky set.

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';
rules[webhookPath] = { ...rules[webhookPath], runtime };
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

runtime is propagated to the flow and webhook routes but not to the manifest route (/.well-known/workflow/v1/manifest.json, registered via addManifestHandler). If a user picks a non-default runtime, the manifest gets served from the catch-all __server function on the default runtime. Low impact (manifest is static JSON), but inconsistent — worth either documenting why it's intentionally excluded, or applying the same override.

return majorVersion === 2;
}
return !nitro.routing;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

This PR adds non-trivial new logic (isNitroV2, the useLegacyVercelBuild branch, the functionRules merge) with no unit test coverage.

Also heads-up: the existing packages/nitro/src/index.test.ts is already broken on main (2 of 10 tests reference #workflow/steps.mjs, which no longer exists since the V1→V2 combined bundle change). Not introduced by this PR, but since the file is already touching the same area, fixing those tests and adding a couple of cases for isNitroV2 and the v3-vercel branch would be a low-cost addition.

Comment threadpackages/nitro/src/builders.ts Outdated
*/
function getNitroStringExternals(nitro: Nitro): string[] | undefined {
const externals = nitro.options.externals?.external?.filter(
const external = (nitro.options as { externals?: { external?: unknown[] } })

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

The structural cast widens external to unknown[] then filters strings — works, but a named alias would read better:

typeNitroV2Options={externals?: {external?: unknown[]}};constexternal=(nitro.optionsasNitroV2Options).externals?.external;

Non-blocking.

Comment threadworkbench/vite/vite.config.ts
The functionRule was keyed by `webhook/**` while the actual handler is
registered at `webhook/:token`, so nitro v3's vercel preset created a
second `webhook/[...].func` next to the real one and shadowed it in the
generated routes. Use the same `:token` pattern as the handler so the
runtime override applies to the function that actually serves traffic.
Also propagate `workflow.runtime` to the public manifest route for
consistency, clarify the merge-precedence comment, and refresh the unit
tests to cover the v3 functionRules path and the `isNitroV2` detection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Wielander <peter.wielander@vercel.com>
@VaguelySerious

Copy link
Copy Markdown
Member

auto-merge is on, waiting for CI to pass

Comment threadpnpm-workspace.yaml
enhanced-resolve: 5.19.0
esbuild: ^0.27.3
nitro: 3.0.1-alpha.1
nitro: ^3.0.260429-beta

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should nitro on the workbench not be a stable release? 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro v2 is nitropack and Nitro v3 is nitro so I think it should be fine. Most the things target Nitro v3 although it's not got a "stable" release yet till Nuxt v5.

@github-actionsgithub-actionsBot mentioned this pull request May 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for c1242e8 (AI decision).

This commit refactors @workflow/nitro to use Nitro v3's functionRules for workflow routes, a code path that does not exist on stable (stable still uses the legacy VercelBuilder for all Vercel deploys regardless of Nitro version). It also bumps the nitro catalog dependency from 3.0.1-alpha.1 to ^3.0.260429-beta, which is a substantial upstream change tied to features only used on main. The webhook pattern fix called out in the changeset only applies to the v3 functionRules path that isn't present on stable.

To override, re-run the Backport to stable workflow manually via workflow_dispatch with this commit SHA.

Sign up for freeto 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.

3 participants

@RihanArfan@VaguelySerious@pi0
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor(nitro): use nitro v3 functionRules workflow routes - #1575

Merged
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement
May 20, 2026
Merged

refactor(nitro): use nitro v3 functionRules workflow routes#1575
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement

Conversation

@RihanArfan

@RihanArfanRihanArfan commented Apr 1, 2026

Copy link
Copy Markdown
Member

Description

Currently we separately build the Workflows functions for the Vercel Build Output API when deploying a Nitro app to Vercel. This means the Workflows routes aren't part of the Nitro bundle.

This PR changes to now handle the Workflows routes as handlers within Nitro v3. Nitro v2 is untouched.

How did you test your changes?

Locally ran nitro dev in the nitro-v3 workspace and visited the API route POST /api/test-direct-step-call.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR
    • During beta, we only use "patch" mode for changes. Don't tag minor/major versions.
    • Use pnpm changeset --empty if you are changing documentation or workbench apps
  • 🔒 DCO sign-off passes (run git commit --signoff on your commits)
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete

@changeset-bot

changeset-botBot commented Apr 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2c843be

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

This PR includes changesets to release 17 packages
NameType
@workflow/nitroPatch
@workflow/nuxtPatch
workflowPatch
@workflow/aiPatch
@workflow/world-testingPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Comment threadpackages/nitro/src/index.ts
@RihanArfan
RihanArfanforce-pushed the feat/nitro-builder-improvement branch from e312e97 to 3eb081aCompareApril 7, 2026 10:28
@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.030s (-29.7% 🟢)1.006s (~)0.975s101.00x
💻 LocalExpress0.031s (-29.6% 🟢)1.005s (~)0.974s101.03x
🐘 PostgresExpress0.043s (-25.5% 🟢)1.010s (~)0.967s101.43x
🐘 PostgresNitro0.048s (-49.2% 🟢)1.010s (-3.1%)0.962s101.60x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.224s (-10.9% 🟢)1.890s (-19.0% 🟢)1.666s101.00x
▲ VercelNitro0.225s (-45.0% 🟢)2.732s (+8.9% 🔺)2.507s101.01x
▲ VercelExpress0.397s (+68.6% 🔺)2.027s (-5.1% 🟢)1.630s101.77x

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

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.072s (-5.3% 🟢)2.007s (~)0.935s101.00x
💻 LocalExpress1.075s (-4.5%)2.006s (~)0.931s101.00x
🐘 PostgresExpress1.082s (-5.6% 🟢)2.009s (~)0.927s101.01x
🐘 PostgresNitro1.083s (-5.0%)2.010s (~)0.927s101.01x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.539s (-17.9% 🟢)3.295s (-13.5% 🟢)1.756s101.00x
▲ VercelNext.js (Turbopack)1.689s (-17.0% 🟢)4.668s (+21.9% 🔺)2.979s101.10x
▲ VercelNitro2.027s (-47.9% 🟢)3.895s (-34.1% 🟢)1.869s101.32x

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

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.412s (-4.7%)11.021s (~)0.608s31.00x
🐘 PostgresExpress10.416s (-5.0%)11.016s (~)0.600s31.00x
💻 LocalNitro10.424s (-4.8%)11.021s (~)0.598s31.00x
🐘 PostgresNitro10.433s (-4.0%)11.020s (~)0.586s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)13.515s (-22.0% 🟢)15.908s (-18.0% 🟢)2.392s21.00x
▲ VercelExpress13.568s (-20.1% 🟢)15.557s (-22.3% 🟢)1.989s21.00x
▲ VercelNitro13.587s (-42.7% 🟢)15.559s (-38.1% 🟢)1.972s21.01x

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

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.456s (-7.7% 🟢)14.017s (-6.7% 🟢)0.561s51.00x
💻 LocalExpress13.459s (-10.1% 🟢)14.025s (-6.7% 🟢)0.566s51.00x
🐘 PostgresNitro13.466s (-7.7% 🟢)14.019s (-6.7% 🟢)0.553s51.00x
💻 LocalNitro13.479s (-10.5% 🟢)14.027s (-12.5% 🟢)0.548s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express20.282s (-59.7% 🟢)22.320s (-57.5% 🟢)2.038s31.00x
▲ VercelNext.js (Turbopack)20.648s (-60.7% 🟢)22.415s (-59.0% 🟢)1.768s31.02x
▲ VercelNitro20.776s (-67.8% 🟢)22.345s (-66.5% 🟢)1.569s31.02x

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

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express11.896s (-28.3% 🟢)12.020s (-29.4% 🟢)0.124s81.00x
💻 LocalNitro11.919s (-29.0% 🟢)12.022s (-29.4% 🟢)0.103s81.00x
🐘 PostgresExpress11.979s (-14.5% 🟢)12.268s (-15.9% 🟢)0.288s81.01x
🐘 PostgresNitro12.158s (-12.9% 🟢)12.872s (-10.0% 🟢)0.714s71.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express30.115s (-75.2% 🟢)32.130s (-74.0% 🟢)2.014s31.00x
▲ VercelNitro30.529s (-92.8% 🟢)32.167s (-92.4% 🟢)1.638s31.01x
▲ VercelNext.js (Turbopack)31.252s (-92.1% 🟢)32.624s (-91.7% 🟢)1.372s31.04x

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

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.138s (-9.7% 🟢)2.007s (~)0.870s151.00x
💻 LocalExpress1.166s (-21.7% 🟢)2.006s (~)0.840s151.02x
💻 LocalNitro1.175s (-28.0% 🟢)2.006s (-3.3%)0.830s151.03x
🐘 PostgresNitro1.238s (-2.9%)2.074s (+3.2%)0.836s151.09x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.605s (-7.5% 🟢)3.989s (-7.7% 🟢)1.383s91.00x
▲ VercelExpress2.632s (-8.0% 🟢)4.521s (-2.2%)1.889s71.01x
▲ VercelNext.js (Turbopack)2.660s (-21.7% 🟢)4.213s (-14.6% 🟢)1.553s81.02x

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

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.199s (-49.2% 🟢)2.006s (-33.3% 🟢)0.807s151.00x
🐘 PostgresNitro1.234s (-47.5% 🟢)2.008s (-33.3% 🟢)0.773s151.03x
💻 LocalExpress1.611s (-45.5% 🟢)2.006s (-41.9% 🟢)0.395s151.34x
💻 LocalNitro1.781s (-43.3% 🟢)2.005s (-48.4% 🟢)0.224s151.49x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.495s (-3.4%)5.288s (+3.5%)1.793s61.00x
▲ VercelNext.js (Turbopack)3.647s (-48.6% 🟢)5.434s (-39.0% 🟢)1.787s61.04x
▲ VercelNitro4.003s (-1.2%)5.826s (-1.6%)1.823s61.15x

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

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.290s (-63.0% 🟢)2.007s (-50.0% 🟢)0.717s151.00x
🐘 PostgresNitro1.350s (-61.2% 🟢)2.008s (-49.9% 🟢)0.659s151.05x
💻 LocalExpress4.222s (-49.4% 🟢)4.582s (-49.2% 🟢)0.361s73.27x
💻 LocalNitro5.125s (-38.6% 🟢)5.678s (-37.1% 🟢)0.554s63.97x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.678s (+32.7% 🔺)6.756s (+22.1% 🔺)2.078s51.00x
▲ VercelExpress6.267s (+47.8% 🔺)8.267s (+34.9% 🔺)2.000s41.34x
▲ VercelNext.js (Turbopack)7.564s (-15.2% 🟢)9.835s (-10.3% 🟢)2.271s41.62x

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

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.145s (-8.9% 🟢)2.008s (~)0.863s151.00x
🐘 PostgresNitro1.149s (-8.6% 🟢)2.008s (~)0.859s151.00x
💻 LocalExpress1.361s (-28.1% 🟢)2.006s (-15.1% 🟢)0.645s151.19x
💻 LocalNitro1.371s (-26.5% 🟢)2.006s (-14.3% 🟢)0.635s151.20x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.498s (+1.6%)4.099s (-1.7%)1.601s81.00x
▲ VercelNext.js (Turbopack)2.715s (-7.4% 🟢)4.421s (-4.8%)1.707s71.09x
▲ VercelExpress2.897s (+12.2% 🔺)4.519s (+3.9%)1.622s71.16x

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

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.192s (-49.1% 🟢)2.008s (-33.3% 🟢)0.817s151.00x
🐘 PostgresExpress1.198s (-48.8% 🟢)2.009s (-33.3% 🟢)0.811s151.01x
💻 LocalExpress1.702s (-45.7% 🟢)2.008s (-46.6% 🟢)0.306s151.43x
💻 LocalNitro2.025s (-33.9% 🟢)2.391s (-38.5% 🟢)0.366s131.70x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.357s (+3.8%)5.248s (+3.4%)1.892s61.00x
▲ VercelExpress3.634s (+13.8% 🔺)5.286s (+10.3% 🔺)1.652s61.08x
▲ VercelNext.js (Turbopack)4.356s (+38.6% 🔺)6.224s (+37.6% 🔺)1.868s61.30x

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

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.271s (-63.7% 🟢)2.007s (-50.0% 🟢)0.735s151.00x
🐘 PostgresNitro1.304s (-62.5% 🟢)2.007s (-49.9% 🟢)0.703s151.03x
💻 LocalExpress4.961s (-43.6% 🟢)5.514s (-40.5% 🟢)0.553s63.90x
💻 LocalNitro6.189s (-32.3% 🟢)6.415s (-36.0% 🟢)0.226s54.87x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.914s (-3.5%)6.839s (~)1.925s51.00x
▲ VercelExpress5.278s (-17.7% 🟢)7.562s (-7.5% 🟢)2.284s41.07x
▲ VercelNext.js (Turbopack)6.788s (~)8.497s (-0.5%)1.709s41.38x

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

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.434s (-48.2% 🟢)1.006s (-1.6%)0.572s601.00x
🐘 PostgresNitro0.454s (-44.7% 🟢)1.024s (+1.8%)0.570s591.05x
💻 LocalNitro0.485s (-50.5% 🟢)1.004s (-8.2% 🟢)0.519s601.12x
💻 LocalExpress0.563s (-42.7% 🟢)1.092s (+1.5%)0.528s571.30x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.763s (-78.4% 🟢)6.470s (-73.1% 🟢)1.707s101.00x
▲ VercelNext.js (Turbopack)5.279s (-63.6% 🟢)6.979s (-56.6% 🟢)1.700s91.11x
▲ VercelExpress5.386s (-71.7% 🟢)7.590s (-64.4% 🟢)2.204s81.13x

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

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.038s (-46.1% 🟢)1.791s (-14.7% 🟢)0.753s511.00x
🐘 PostgresExpress1.094s (-44.7% 🟢)1.705s (-24.5% 🟢)0.611s531.05x
💻 LocalExpress1.163s (-61.4% 🟢)2.005s (-44.1% 🟢)0.842s451.12x
💻 LocalNitro1.200s (-60.5% 🟢)2.006s (-46.6% 🟢)0.806s451.16x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.272s (-68.9% 🟢)14.115s (-65.8% 🟢)1.842s71.00x
▲ VercelNext.js (Turbopack)13.606s (-72.7% 🟢)15.509s (-70.0% 🟢)1.903s61.11x
▲ VercelExpress13.810s (-60.0% 🟢)16.683s (-54.7% 🟢)2.872s61.13x

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

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.047s (-48.7% 🟢)2.506s (-42.6% 🟢)0.459s481.00x
🐘 PostgresNitro2.132s (-48.0% 🟢)2.798s (-39.2% 🟢)0.666s431.04x
💻 LocalExpress2.603s (-71.7% 🟢)3.007s (-70.0% 🟢)0.404s401.27x
💻 LocalNitro2.699s (-71.0% 🟢)3.008s (-70.0% 🟢)0.308s401.32x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro39.315s (-59.4% 🟢)41.156s (-58.2% 🟢)1.841s31.00x
▲ VercelExpress40.781s (-68.6% 🟢)43.325s (-67.2% 🟢)2.544s31.04x
▲ VercelNext.js (Turbopack)41.245s (-61.5% 🟢)42.926s (-60.6% 🟢)1.681s31.05x

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

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.165s (-41.6% 🟢)1.006s (~)0.841s601.00x
🐘 PostgresNitro0.172s (-39.4% 🟢)1.006s (~)0.834s601.04x
💻 LocalExpress0.421s (-24.9% 🟢)1.004s (~)0.583s602.55x
💻 LocalNitro0.463s (-23.5% 🟢)1.004s (-1.7%)0.541s602.81x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.161s (+10.6% 🔺)4.409s (+21.2% 🔺)2.249s141.00x
▲ VercelNext.js (Turbopack)2.599s (+28.5% 🔺)4.308s (+13.6% 🔺)1.709s141.20x
▲ VercelNitro2.656s (+59.9% 🔺)4.513s (+34.7% 🔺)1.858s141.23x

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

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.285s (-44.0% 🟢)1.006s (~)0.720s901.00x
🐘 PostgresNitro0.298s (-40.0% 🟢)1.006s (~)0.708s901.04x
💻 LocalExpress2.156s (-14.2% 🟢)2.685s (-10.8% 🟢)0.529s347.56x
💻 LocalNitro2.179s (-14.2% 🟢)2.912s (-3.2%)0.733s317.64x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)8.136s (+130.1% 🔺)10.219s (+96.8% 🔺)2.083s91.00x
▲ VercelExpress8.249s (+170.8% 🔺)10.683s (+122.2% 🔺)2.434s91.01x
▲ VercelNitro9.696s (+200.6% 🔺)11.640s (+141.4% 🔺)1.944s81.19x

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

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.558s (-31.8% 🟢)1.005s (-1.2%)0.447s1201.00x
🐘 PostgresNitro0.652s (-17.5% 🟢)1.006s (~)0.354s1201.17x
💻 LocalExpress9.153s (-18.2% 🟢)9.795s (-18.0% 🟢)0.643s1316.40x
💻 LocalNitro10.117s (-9.6% 🟢)10.778s (-7.6% 🟢)0.661s1218.13x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.676s (+119.6% 🔺)24.944s (+103.0% 🔺)2.268s51.00x
▲ VercelNitro23.736s (+207.4% 🔺)25.097s (+167.0% 🔺)1.361s51.05x
▲ VercelExpress28.260s (+280.9% 🔺)30.473s (+229.6% 🔺)2.213s41.25x

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

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.126s (+465.4% 🔺)2.004s (+99.5% 🔺)0.010s (-19.8% 🟢)2.016s (+98.0% 🔺)0.890s101.00x
🐘 PostgresExpress1.133s (+452.5% 🔺)2.002s (+100.5% 🔺)0.001s (-25.0% 🟢)2.010s (+98.8% 🔺)0.877s101.01x
💻 LocalNitro1.143s (+434.8% 🔺)2.005s (+99.6% 🔺)0.012s (-2.4%)2.019s (+98.2% 🔺)0.877s101.02x
🐘 PostgresNitro1.157s (+464.4% 🔺)1.999s (+100.0% 🔺)0.001s (-20.0% 🟢)2.010s (+98.8% 🔺)0.853s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)2.185s (-68.1% 🟢)3.165s (-63.4% 🟢)1.925s (+204.7% 🔺)5.496s (-43.9% 🟢)3.311s101.00x
▲ VercelNitro2.295s (-40.1% 🟢)3.603s (-31.7% 🟢)13.820s (+1762.3% 🔺)17.886s (+175.9% 🔺)15.591s101.05x
▲ VercelExpress2.329s (-7.0% 🟢)3.832s (-6.3% 🟢)1.518s (+58.0% 🔺)5.905s (+5.6% 🔺)3.576s101.07x

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

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.490s (+136.5% 🔺)2.005s (+99.2% 🔺)0.003s (-12.1% 🟢)2.022s (+97.7% 🔺)0.532s301.00x
🐘 PostgresNitro1.528s (+144.8% 🔺)2.003s (+98.9% 🔺)0.004s (-3.3%)2.027s (+98.2% 🔺)0.499s301.03x
💻 LocalNitro1.707s (+103.5% 🔺)2.011s (+98.7% 🔺)0.010s (+6.7% 🔺)2.202s (+97.3% 🔺)0.495s281.15x
💻 LocalExpress1.877s (+148.0% 🔺)2.011s (+95.5% 🔺)0.009s (-0.9%)2.394s (+130.2% 🔺)0.517s271.26x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.935s (-8.8% 🟢)7.705s (-3.8%)0.216s (-47.0% 🟢)8.447s (-4.4%)2.512s81.00x
▲ VercelNitro5.975s (-79.7% 🟢)7.548s (-75.5% 🟢)0.263s (+134.7% 🔺)8.277s (-74.0% 🟢)2.302s81.01x
▲ VercelNext.js (Turbopack)6.750s (-60.1% 🟢)7.706s (-57.8% 🟢)0.297s (+40.7% 🔺)8.648s (-54.3% 🟢)1.898s71.14x

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

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.637s (-34.3% 🟢)1.034s (-17.1% 🟢)0.000s (-58.6% 🟢)1.042s (-17.2% 🟢)0.405s581.00x
🐘 PostgresExpress0.647s (-32.7% 🟢)1.050s (-17.8% 🟢)0.000s (+58.6% 🔺)1.060s (-18.8% 🟢)0.413s581.02x
💻 LocalExpress1.339s (+9.3% 🔺)2.016s (~)0.001s (+70.0% 🔺)2.018s (~)0.679s302.10x
💻 LocalNitro1.726s (+41.2% 🔺)2.015s (~)0.000s (+140.0% 🔺)2.418s (+19.6% 🔺)0.691s252.71x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)3.534s (-65.3% 🟢)4.591s (-60.1% 🟢)0.000s (NaN%)5.094s (-57.7% 🟢)1.560s121.00x
▲ VercelNitro3.538s (+16.0% 🔺)4.997s (+13.8% 🔺)0.000s (+225.0% 🔺)5.453s (+13.4% 🔺)1.916s121.00x
▲ VercelExpress3.646s (-2.5%)5.083s (~)0.000s (~)5.696s (+3.0%)2.050s111.03x

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

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.294s (-27.0% 🟢)1.905s (-12.5% 🟢)0.000s (NaN%)1.924s (-12.5% 🟢)0.630s321.00x
🐘 PostgresNitro1.320s (-26.3% 🟢)2.031s (-5.1% 🟢)0.000s (-100.0% 🟢)2.043s (-6.0% 🟢)0.723s301.02x
💻 LocalExpress3.031s (-12.6% 🟢)3.777s (-6.4% 🟢)0.001s (-21.9% 🟢)3.780s (-6.3% 🟢)0.749s162.34x
💻 LocalNitro3.076s (-9.2% 🟢)3.777s (-6.3% 🟢)0.001s (-6.2% 🟢)3.779s (-6.4% 🟢)0.703s162.38x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.447s (+33.1% 🔺)7.047s (+31.1% 🔺)0.000s (-54.2% 🟢)7.517s (+29.7% 🔺)2.070s81.00x
▲ VercelNext.js (Turbopack)5.517s (-1.8%)7.043s (+0.9%)0.000s (~)7.537s (~)2.020s81.01x
▲ VercelExpress6.217s (+35.5% 🔺)7.655s (+27.1% 🔺)0.000s (NaN%)8.501s (+31.7% 🔺)2.284s81.14x

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

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress17/21
🐘 PostgresExpress18/21
▲ VercelNitro9/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)▲ Vercel21/21
Nitro🐘 Postgres15/21
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)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production120002191419
✅ 💻 Local Development158702191806
✅ 📦 Local Production158702191806
✅ 🐘 Local Postgres158702191806
✅ 🪟 Windows12900129
✅ 📋 Other7270176903
Total6817010527869

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro103026
✅ example103026
✅ express103026
✅ fastify103026
✅ hono103026
✅ nextjs-turbopack12702
✅ nextjs-webpack12702
✅ nitro103026
✅ nuxt103026
✅ sveltekit12207
✅ vite103026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack12900
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable104025
✅ e2e-local-dev-tanstack-start-104025
✅ e2e-local-postgres-nest-stable104025
✅ e2e-local-postgres-tanstack-start-104025
✅ e2e-local-prod-nest-stable104025
✅ e2e-local-prod-tanstack-start-104025
✅ e2e-vercel-prod-tanstack-start103026

📋 View full workflow run

Comment threadpackages/nitro/src/index.ts Outdated
@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably know better than me whether the nits apply, so use your judgement there. Happy to approve this once the e2e tests are running again and CI is green

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: blocking issues found

Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.test.ts
Comment threadpackages/builders/src/types.ts Outdated
* Extra esbuild options merged into every bundle this builder produces
* (steps, intermediate workflow, final workflow wrapper, webhook).
*/
esbuildOptions?: Partial<BuildOptions>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

esbuildOptions: Partial<BuildOptions> is a broad surface for one knob

The new field accepts the full esbuild BuildOptions and is spread into four bundle calls with very different needs (steps: node/esm, workflow VM: neutral/cjs, webhook: node/esm, final wrapper: node/esm). The current spread order puts user options first so builder-required options like platform, format, bundle, plugins win — that's defensive — but the API still implies "you can pass any esbuild option" when in practice almost everything that matters is overridden.

Given the only consumer is sourcesContent: false, consider either narrowing the public type to a small allowlist or naming the field something closer to its actual purpose. Not blocking, but the surface area will be hard to take back later.

@VaguelySerious

Copy link
Copy Markdown
Member

Seems like tests are still failing, please re-ping me when they pass 👋

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

Hey @VaguelySerious, the related tests are passing 🥳

@vercel

vercelBot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Deployment failed with the following error:

The Preview Deployment Suffix domain "labs.vercel.dev" is not available under the current scope. Learn more: https://vercel.link/3a1

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: no blocking issues

Comment threadpackages/nitro/src/index.ts Outdated
};

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The webhook handler is registered at /.well-known/workflow/v1/webhook/:token (line 214), but this functionRules key uses webhook/**. I built workbench/nitro-v3 with NITRO_PRESET=vercel and workflow.runtime: 'nodejs22.x' and verified that nitro's vercel preset generates two function dirs in this case:

  • webhook/[token].func — created via routeFuncRouter.match against the :token route, gets the runtime override.
  • webhook/[...].func — created from this ** rule, also gets the runtime override.

In config.json, the webhook/(?:.*) route emitted by this rule comes before webhook/(?<token>[^/]+), so [token].func ends up unreachable. The result is a wasted function dir (extra disk/build/deploy size) and slightly looser routing (/webhook/ with no token now matches).

Suggested fix: use the same pattern as the handler, e.g. '/.well-known/workflow/v1/webhook/:token'. With matching keys, nitro reuses the single funcDir under the runtime override.

// V2 combined: a single trigger covers both `__wkf_workflow_*`
// (workflow orchestration) and `__wkf_step_*` (step execution),
// since the same handler dispatches both.
experimentalTriggers: [WORKFLOW_QUEUE_TRIGGER],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The comment two lines above says "merged with any user-defined rules at the same paths so explicit user config (e.g. memory) is preserved", but the spread order here (user first, then maxDuration / experimentalTriggers) means workflow's values always win. Only fields that workflow doesn't touch (like memory) survive — user-set maxDuration, runtime, or additional experimentalTriggers get clobbered.

Two small things:

  1. The comment should match the behavior — something like "workflow's required triggers/maxDuration take precedence; unrelated fields like memory are preserved."
  2. Other integrations (e.g. packages/sveltekit) concatenate user triggers rather than replacing them. Probably not an issue in practice since this is a workflow-owned route, but worth deciding deliberately.

Also (related): also exercised by the E2E Vercel Prod Tests (hono) job, which currently fails on maxRetries=0 disables retries (60s timeout) on this PR while passing on main. And E2E Local Prod Tests (tanstack-start) fails on abortVoidSleepTimeoutWorkflow (also passes on main). Both look like potential regressions worth investigating before merge — they aren't in the known-flaky set.

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';
rules[webhookPath] = { ...rules[webhookPath], runtime };
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

runtime is propagated to the flow and webhook routes but not to the manifest route (/.well-known/workflow/v1/manifest.json, registered via addManifestHandler). If a user picks a non-default runtime, the manifest gets served from the catch-all __server function on the default runtime. Low impact (manifest is static JSON), but inconsistent — worth either documenting why it's intentionally excluded, or applying the same override.

return majorVersion === 2;
}
return !nitro.routing;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

This PR adds non-trivial new logic (isNitroV2, the useLegacyVercelBuild branch, the functionRules merge) with no unit test coverage.

Also heads-up: the existing packages/nitro/src/index.test.ts is already broken on main (2 of 10 tests reference #workflow/steps.mjs, which no longer exists since the V1→V2 combined bundle change). Not introduced by this PR, but since the file is already touching the same area, fixing those tests and adding a couple of cases for isNitroV2 and the v3-vercel branch would be a low-cost addition.

Comment threadpackages/nitro/src/builders.ts Outdated
*/
function getNitroStringExternals(nitro: Nitro): string[] | undefined {
const externals = nitro.options.externals?.external?.filter(
const external = (nitro.options as { externals?: { external?: unknown[] } })

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

The structural cast widens external to unknown[] then filters strings — works, but a named alias would read better:

typeNitroV2Options={externals?: {external?: unknown[]}};constexternal=(nitro.optionsasNitroV2Options).externals?.external;

Non-blocking.

Comment threadworkbench/vite/vite.config.ts
The functionRule was keyed by `webhook/**` while the actual handler is
registered at `webhook/:token`, so nitro v3's vercel preset created a
second `webhook/[...].func` next to the real one and shadowed it in the
generated routes. Use the same `:token` pattern as the handler so the
runtime override applies to the function that actually serves traffic.
Also propagate `workflow.runtime` to the public manifest route for
consistency, clarify the merge-precedence comment, and refresh the unit
tests to cover the v3 functionRules path and the `isNitroV2` detection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Wielander <peter.wielander@vercel.com>
@VaguelySerious

Copy link
Copy Markdown
Member

auto-merge is on, waiting for CI to pass

Comment threadpnpm-workspace.yaml
enhanced-resolve: 5.19.0
esbuild: ^0.27.3
nitro: 3.0.1-alpha.1
nitro: ^3.0.260429-beta

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should nitro on the workbench not be a stable release? 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro v2 is nitropack and Nitro v3 is nitro so I think it should be fine. Most the things target Nitro v3 although it's not got a "stable" release yet till Nuxt v5.

@github-actionsgithub-actionsBot mentioned this pull request May 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for c1242e8 (AI decision).

This commit refactors @workflow/nitro to use Nitro v3's functionRules for workflow routes, a code path that does not exist on stable (stable still uses the legacy VercelBuilder for all Vercel deploys regardless of Nitro version). It also bumps the nitro catalog dependency from 3.0.1-alpha.1 to ^3.0.260429-beta, which is a substantial upstream change tied to features only used on main. The webhook pattern fix called out in the changeset only applies to the v3 functionRules path that isn't present on stable.

To override, re-run the Backport to stable workflow manually via workflow_dispatch with this commit SHA.

Sign up for freeto 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.

3 participants

@RihanArfan@VaguelySerious@pi0
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

refactor(nitro): use nitro v3 functionRules workflow routes - #1575

Merged
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement
May 20, 2026
Merged

refactor(nitro): use nitro v3 functionRules workflow routes#1575
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement

Conversation

@RihanArfan

@RihanArfanRihanArfan commented Apr 1, 2026

Copy link
Copy Markdown
Member

Description

Currently we separately build the Workflows functions for the Vercel Build Output API when deploying a Nitro app to Vercel. This means the Workflows routes aren't part of the Nitro bundle.

This PR changes to now handle the Workflows routes as handlers within Nitro v3. Nitro v2 is untouched.

How did you test your changes?

Locally ran nitro dev in the nitro-v3 workspace and visited the API route POST /api/test-direct-step-call.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR
    • During beta, we only use "patch" mode for changes. Don't tag minor/major versions.
    • Use pnpm changeset --empty if you are changing documentation or workbench apps
  • 🔒 DCO sign-off passes (run git commit --signoff on your commits)
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete

@changeset-bot

changeset-botBot commented Apr 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2c843be

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

This PR includes changesets to release 17 packages
NameType
@workflow/nitroPatch
@workflow/nuxtPatch
workflowPatch
@workflow/aiPatch
@workflow/world-testingPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Comment threadpackages/nitro/src/index.ts
@RihanArfan
RihanArfanforce-pushed the feat/nitro-builder-improvement branch from e312e97 to 3eb081aCompareApril 7, 2026 10:28
@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.030s (-29.7% 🟢)1.006s (~)0.975s101.00x
💻 LocalExpress0.031s (-29.6% 🟢)1.005s (~)0.974s101.03x
🐘 PostgresExpress0.043s (-25.5% 🟢)1.010s (~)0.967s101.43x
🐘 PostgresNitro0.048s (-49.2% 🟢)1.010s (-3.1%)0.962s101.60x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.224s (-10.9% 🟢)1.890s (-19.0% 🟢)1.666s101.00x
▲ VercelNitro0.225s (-45.0% 🟢)2.732s (+8.9% 🔺)2.507s101.01x
▲ VercelExpress0.397s (+68.6% 🔺)2.027s (-5.1% 🟢)1.630s101.77x

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

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.072s (-5.3% 🟢)2.007s (~)0.935s101.00x
💻 LocalExpress1.075s (-4.5%)2.006s (~)0.931s101.00x
🐘 PostgresExpress1.082s (-5.6% 🟢)2.009s (~)0.927s101.01x
🐘 PostgresNitro1.083s (-5.0%)2.010s (~)0.927s101.01x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.539s (-17.9% 🟢)3.295s (-13.5% 🟢)1.756s101.00x
▲ VercelNext.js (Turbopack)1.689s (-17.0% 🟢)4.668s (+21.9% 🔺)2.979s101.10x
▲ VercelNitro2.027s (-47.9% 🟢)3.895s (-34.1% 🟢)1.869s101.32x

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

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.412s (-4.7%)11.021s (~)0.608s31.00x
🐘 PostgresExpress10.416s (-5.0%)11.016s (~)0.600s31.00x
💻 LocalNitro10.424s (-4.8%)11.021s (~)0.598s31.00x
🐘 PostgresNitro10.433s (-4.0%)11.020s (~)0.586s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)13.515s (-22.0% 🟢)15.908s (-18.0% 🟢)2.392s21.00x
▲ VercelExpress13.568s (-20.1% 🟢)15.557s (-22.3% 🟢)1.989s21.00x
▲ VercelNitro13.587s (-42.7% 🟢)15.559s (-38.1% 🟢)1.972s21.01x

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

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.456s (-7.7% 🟢)14.017s (-6.7% 🟢)0.561s51.00x
💻 LocalExpress13.459s (-10.1% 🟢)14.025s (-6.7% 🟢)0.566s51.00x
🐘 PostgresNitro13.466s (-7.7% 🟢)14.019s (-6.7% 🟢)0.553s51.00x
💻 LocalNitro13.479s (-10.5% 🟢)14.027s (-12.5% 🟢)0.548s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express20.282s (-59.7% 🟢)22.320s (-57.5% 🟢)2.038s31.00x
▲ VercelNext.js (Turbopack)20.648s (-60.7% 🟢)22.415s (-59.0% 🟢)1.768s31.02x
▲ VercelNitro20.776s (-67.8% 🟢)22.345s (-66.5% 🟢)1.569s31.02x

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

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express11.896s (-28.3% 🟢)12.020s (-29.4% 🟢)0.124s81.00x
💻 LocalNitro11.919s (-29.0% 🟢)12.022s (-29.4% 🟢)0.103s81.00x
🐘 PostgresExpress11.979s (-14.5% 🟢)12.268s (-15.9% 🟢)0.288s81.01x
🐘 PostgresNitro12.158s (-12.9% 🟢)12.872s (-10.0% 🟢)0.714s71.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express30.115s (-75.2% 🟢)32.130s (-74.0% 🟢)2.014s31.00x
▲ VercelNitro30.529s (-92.8% 🟢)32.167s (-92.4% 🟢)1.638s31.01x
▲ VercelNext.js (Turbopack)31.252s (-92.1% 🟢)32.624s (-91.7% 🟢)1.372s31.04x

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

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.138s (-9.7% 🟢)2.007s (~)0.870s151.00x
💻 LocalExpress1.166s (-21.7% 🟢)2.006s (~)0.840s151.02x
💻 LocalNitro1.175s (-28.0% 🟢)2.006s (-3.3%)0.830s151.03x
🐘 PostgresNitro1.238s (-2.9%)2.074s (+3.2%)0.836s151.09x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.605s (-7.5% 🟢)3.989s (-7.7% 🟢)1.383s91.00x
▲ VercelExpress2.632s (-8.0% 🟢)4.521s (-2.2%)1.889s71.01x
▲ VercelNext.js (Turbopack)2.660s (-21.7% 🟢)4.213s (-14.6% 🟢)1.553s81.02x

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

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.199s (-49.2% 🟢)2.006s (-33.3% 🟢)0.807s151.00x
🐘 PostgresNitro1.234s (-47.5% 🟢)2.008s (-33.3% 🟢)0.773s151.03x
💻 LocalExpress1.611s (-45.5% 🟢)2.006s (-41.9% 🟢)0.395s151.34x
💻 LocalNitro1.781s (-43.3% 🟢)2.005s (-48.4% 🟢)0.224s151.49x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.495s (-3.4%)5.288s (+3.5%)1.793s61.00x
▲ VercelNext.js (Turbopack)3.647s (-48.6% 🟢)5.434s (-39.0% 🟢)1.787s61.04x
▲ VercelNitro4.003s (-1.2%)5.826s (-1.6%)1.823s61.15x

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

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.290s (-63.0% 🟢)2.007s (-50.0% 🟢)0.717s151.00x
🐘 PostgresNitro1.350s (-61.2% 🟢)2.008s (-49.9% 🟢)0.659s151.05x
💻 LocalExpress4.222s (-49.4% 🟢)4.582s (-49.2% 🟢)0.361s73.27x
💻 LocalNitro5.125s (-38.6% 🟢)5.678s (-37.1% 🟢)0.554s63.97x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.678s (+32.7% 🔺)6.756s (+22.1% 🔺)2.078s51.00x
▲ VercelExpress6.267s (+47.8% 🔺)8.267s (+34.9% 🔺)2.000s41.34x
▲ VercelNext.js (Turbopack)7.564s (-15.2% 🟢)9.835s (-10.3% 🟢)2.271s41.62x

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

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.145s (-8.9% 🟢)2.008s (~)0.863s151.00x
🐘 PostgresNitro1.149s (-8.6% 🟢)2.008s (~)0.859s151.00x
💻 LocalExpress1.361s (-28.1% 🟢)2.006s (-15.1% 🟢)0.645s151.19x
💻 LocalNitro1.371s (-26.5% 🟢)2.006s (-14.3% 🟢)0.635s151.20x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.498s (+1.6%)4.099s (-1.7%)1.601s81.00x
▲ VercelNext.js (Turbopack)2.715s (-7.4% 🟢)4.421s (-4.8%)1.707s71.09x
▲ VercelExpress2.897s (+12.2% 🔺)4.519s (+3.9%)1.622s71.16x

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

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.192s (-49.1% 🟢)2.008s (-33.3% 🟢)0.817s151.00x
🐘 PostgresExpress1.198s (-48.8% 🟢)2.009s (-33.3% 🟢)0.811s151.01x
💻 LocalExpress1.702s (-45.7% 🟢)2.008s (-46.6% 🟢)0.306s151.43x
💻 LocalNitro2.025s (-33.9% 🟢)2.391s (-38.5% 🟢)0.366s131.70x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.357s (+3.8%)5.248s (+3.4%)1.892s61.00x
▲ VercelExpress3.634s (+13.8% 🔺)5.286s (+10.3% 🔺)1.652s61.08x
▲ VercelNext.js (Turbopack)4.356s (+38.6% 🔺)6.224s (+37.6% 🔺)1.868s61.30x

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

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.271s (-63.7% 🟢)2.007s (-50.0% 🟢)0.735s151.00x
🐘 PostgresNitro1.304s (-62.5% 🟢)2.007s (-49.9% 🟢)0.703s151.03x
💻 LocalExpress4.961s (-43.6% 🟢)5.514s (-40.5% 🟢)0.553s63.90x
💻 LocalNitro6.189s (-32.3% 🟢)6.415s (-36.0% 🟢)0.226s54.87x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.914s (-3.5%)6.839s (~)1.925s51.00x
▲ VercelExpress5.278s (-17.7% 🟢)7.562s (-7.5% 🟢)2.284s41.07x
▲ VercelNext.js (Turbopack)6.788s (~)8.497s (-0.5%)1.709s41.38x

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

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.434s (-48.2% 🟢)1.006s (-1.6%)0.572s601.00x
🐘 PostgresNitro0.454s (-44.7% 🟢)1.024s (+1.8%)0.570s591.05x
💻 LocalNitro0.485s (-50.5% 🟢)1.004s (-8.2% 🟢)0.519s601.12x
💻 LocalExpress0.563s (-42.7% 🟢)1.092s (+1.5%)0.528s571.30x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.763s (-78.4% 🟢)6.470s (-73.1% 🟢)1.707s101.00x
▲ VercelNext.js (Turbopack)5.279s (-63.6% 🟢)6.979s (-56.6% 🟢)1.700s91.11x
▲ VercelExpress5.386s (-71.7% 🟢)7.590s (-64.4% 🟢)2.204s81.13x

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

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.038s (-46.1% 🟢)1.791s (-14.7% 🟢)0.753s511.00x
🐘 PostgresExpress1.094s (-44.7% 🟢)1.705s (-24.5% 🟢)0.611s531.05x
💻 LocalExpress1.163s (-61.4% 🟢)2.005s (-44.1% 🟢)0.842s451.12x
💻 LocalNitro1.200s (-60.5% 🟢)2.006s (-46.6% 🟢)0.806s451.16x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.272s (-68.9% 🟢)14.115s (-65.8% 🟢)1.842s71.00x
▲ VercelNext.js (Turbopack)13.606s (-72.7% 🟢)15.509s (-70.0% 🟢)1.903s61.11x
▲ VercelExpress13.810s (-60.0% 🟢)16.683s (-54.7% 🟢)2.872s61.13x

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

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.047s (-48.7% 🟢)2.506s (-42.6% 🟢)0.459s481.00x
🐘 PostgresNitro2.132s (-48.0% 🟢)2.798s (-39.2% 🟢)0.666s431.04x
💻 LocalExpress2.603s (-71.7% 🟢)3.007s (-70.0% 🟢)0.404s401.27x
💻 LocalNitro2.699s (-71.0% 🟢)3.008s (-70.0% 🟢)0.308s401.32x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro39.315s (-59.4% 🟢)41.156s (-58.2% 🟢)1.841s31.00x
▲ VercelExpress40.781s (-68.6% 🟢)43.325s (-67.2% 🟢)2.544s31.04x
▲ VercelNext.js (Turbopack)41.245s (-61.5% 🟢)42.926s (-60.6% 🟢)1.681s31.05x

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

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.165s (-41.6% 🟢)1.006s (~)0.841s601.00x
🐘 PostgresNitro0.172s (-39.4% 🟢)1.006s (~)0.834s601.04x
💻 LocalExpress0.421s (-24.9% 🟢)1.004s (~)0.583s602.55x
💻 LocalNitro0.463s (-23.5% 🟢)1.004s (-1.7%)0.541s602.81x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.161s (+10.6% 🔺)4.409s (+21.2% 🔺)2.249s141.00x
▲ VercelNext.js (Turbopack)2.599s (+28.5% 🔺)4.308s (+13.6% 🔺)1.709s141.20x
▲ VercelNitro2.656s (+59.9% 🔺)4.513s (+34.7% 🔺)1.858s141.23x

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

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.285s (-44.0% 🟢)1.006s (~)0.720s901.00x
🐘 PostgresNitro0.298s (-40.0% 🟢)1.006s (~)0.708s901.04x
💻 LocalExpress2.156s (-14.2% 🟢)2.685s (-10.8% 🟢)0.529s347.56x
💻 LocalNitro2.179s (-14.2% 🟢)2.912s (-3.2%)0.733s317.64x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)8.136s (+130.1% 🔺)10.219s (+96.8% 🔺)2.083s91.00x
▲ VercelExpress8.249s (+170.8% 🔺)10.683s (+122.2% 🔺)2.434s91.01x
▲ VercelNitro9.696s (+200.6% 🔺)11.640s (+141.4% 🔺)1.944s81.19x

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

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.558s (-31.8% 🟢)1.005s (-1.2%)0.447s1201.00x
🐘 PostgresNitro0.652s (-17.5% 🟢)1.006s (~)0.354s1201.17x
💻 LocalExpress9.153s (-18.2% 🟢)9.795s (-18.0% 🟢)0.643s1316.40x
💻 LocalNitro10.117s (-9.6% 🟢)10.778s (-7.6% 🟢)0.661s1218.13x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.676s (+119.6% 🔺)24.944s (+103.0% 🔺)2.268s51.00x
▲ VercelNitro23.736s (+207.4% 🔺)25.097s (+167.0% 🔺)1.361s51.05x
▲ VercelExpress28.260s (+280.9% 🔺)30.473s (+229.6% 🔺)2.213s41.25x

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

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.126s (+465.4% 🔺)2.004s (+99.5% 🔺)0.010s (-19.8% 🟢)2.016s (+98.0% 🔺)0.890s101.00x
🐘 PostgresExpress1.133s (+452.5% 🔺)2.002s (+100.5% 🔺)0.001s (-25.0% 🟢)2.010s (+98.8% 🔺)0.877s101.01x
💻 LocalNitro1.143s (+434.8% 🔺)2.005s (+99.6% 🔺)0.012s (-2.4%)2.019s (+98.2% 🔺)0.877s101.02x
🐘 PostgresNitro1.157s (+464.4% 🔺)1.999s (+100.0% 🔺)0.001s (-20.0% 🟢)2.010s (+98.8% 🔺)0.853s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)2.185s (-68.1% 🟢)3.165s (-63.4% 🟢)1.925s (+204.7% 🔺)5.496s (-43.9% 🟢)3.311s101.00x
▲ VercelNitro2.295s (-40.1% 🟢)3.603s (-31.7% 🟢)13.820s (+1762.3% 🔺)17.886s (+175.9% 🔺)15.591s101.05x
▲ VercelExpress2.329s (-7.0% 🟢)3.832s (-6.3% 🟢)1.518s (+58.0% 🔺)5.905s (+5.6% 🔺)3.576s101.07x

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

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.490s (+136.5% 🔺)2.005s (+99.2% 🔺)0.003s (-12.1% 🟢)2.022s (+97.7% 🔺)0.532s301.00x
🐘 PostgresNitro1.528s (+144.8% 🔺)2.003s (+98.9% 🔺)0.004s (-3.3%)2.027s (+98.2% 🔺)0.499s301.03x
💻 LocalNitro1.707s (+103.5% 🔺)2.011s (+98.7% 🔺)0.010s (+6.7% 🔺)2.202s (+97.3% 🔺)0.495s281.15x
💻 LocalExpress1.877s (+148.0% 🔺)2.011s (+95.5% 🔺)0.009s (-0.9%)2.394s (+130.2% 🔺)0.517s271.26x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.935s (-8.8% 🟢)7.705s (-3.8%)0.216s (-47.0% 🟢)8.447s (-4.4%)2.512s81.00x
▲ VercelNitro5.975s (-79.7% 🟢)7.548s (-75.5% 🟢)0.263s (+134.7% 🔺)8.277s (-74.0% 🟢)2.302s81.01x
▲ VercelNext.js (Turbopack)6.750s (-60.1% 🟢)7.706s (-57.8% 🟢)0.297s (+40.7% 🔺)8.648s (-54.3% 🟢)1.898s71.14x

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

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.637s (-34.3% 🟢)1.034s (-17.1% 🟢)0.000s (-58.6% 🟢)1.042s (-17.2% 🟢)0.405s581.00x
🐘 PostgresExpress0.647s (-32.7% 🟢)1.050s (-17.8% 🟢)0.000s (+58.6% 🔺)1.060s (-18.8% 🟢)0.413s581.02x
💻 LocalExpress1.339s (+9.3% 🔺)2.016s (~)0.001s (+70.0% 🔺)2.018s (~)0.679s302.10x
💻 LocalNitro1.726s (+41.2% 🔺)2.015s (~)0.000s (+140.0% 🔺)2.418s (+19.6% 🔺)0.691s252.71x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)3.534s (-65.3% 🟢)4.591s (-60.1% 🟢)0.000s (NaN%)5.094s (-57.7% 🟢)1.560s121.00x
▲ VercelNitro3.538s (+16.0% 🔺)4.997s (+13.8% 🔺)0.000s (+225.0% 🔺)5.453s (+13.4% 🔺)1.916s121.00x
▲ VercelExpress3.646s (-2.5%)5.083s (~)0.000s (~)5.696s (+3.0%)2.050s111.03x

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

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.294s (-27.0% 🟢)1.905s (-12.5% 🟢)0.000s (NaN%)1.924s (-12.5% 🟢)0.630s321.00x
🐘 PostgresNitro1.320s (-26.3% 🟢)2.031s (-5.1% 🟢)0.000s (-100.0% 🟢)2.043s (-6.0% 🟢)0.723s301.02x
💻 LocalExpress3.031s (-12.6% 🟢)3.777s (-6.4% 🟢)0.001s (-21.9% 🟢)3.780s (-6.3% 🟢)0.749s162.34x
💻 LocalNitro3.076s (-9.2% 🟢)3.777s (-6.3% 🟢)0.001s (-6.2% 🟢)3.779s (-6.4% 🟢)0.703s162.38x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.447s (+33.1% 🔺)7.047s (+31.1% 🔺)0.000s (-54.2% 🟢)7.517s (+29.7% 🔺)2.070s81.00x
▲ VercelNext.js (Turbopack)5.517s (-1.8%)7.043s (+0.9%)0.000s (~)7.537s (~)2.020s81.01x
▲ VercelExpress6.217s (+35.5% 🔺)7.655s (+27.1% 🔺)0.000s (NaN%)8.501s (+31.7% 🔺)2.284s81.14x

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

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress17/21
🐘 PostgresExpress18/21
▲ VercelNitro9/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)▲ Vercel21/21
Nitro🐘 Postgres15/21
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)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production120002191419
✅ 💻 Local Development158702191806
✅ 📦 Local Production158702191806
✅ 🐘 Local Postgres158702191806
✅ 🪟 Windows12900129
✅ 📋 Other7270176903
Total6817010527869

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro103026
✅ example103026
✅ express103026
✅ fastify103026
✅ hono103026
✅ nextjs-turbopack12702
✅ nextjs-webpack12702
✅ nitro103026
✅ nuxt103026
✅ sveltekit12207
✅ vite103026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack12900
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable104025
✅ e2e-local-dev-tanstack-start-104025
✅ e2e-local-postgres-nest-stable104025
✅ e2e-local-postgres-tanstack-start-104025
✅ e2e-local-prod-nest-stable104025
✅ e2e-local-prod-tanstack-start-104025
✅ e2e-vercel-prod-tanstack-start103026

📋 View full workflow run

Comment threadpackages/nitro/src/index.ts Outdated
@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably know better than me whether the nits apply, so use your judgement there. Happy to approve this once the e2e tests are running again and CI is green

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: blocking issues found

Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.test.ts
Comment threadpackages/builders/src/types.ts Outdated
* Extra esbuild options merged into every bundle this builder produces
* (steps, intermediate workflow, final workflow wrapper, webhook).
*/
esbuildOptions?: Partial<BuildOptions>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

esbuildOptions: Partial<BuildOptions> is a broad surface for one knob

The new field accepts the full esbuild BuildOptions and is spread into four bundle calls with very different needs (steps: node/esm, workflow VM: neutral/cjs, webhook: node/esm, final wrapper: node/esm). The current spread order puts user options first so builder-required options like platform, format, bundle, plugins win — that's defensive — but the API still implies "you can pass any esbuild option" when in practice almost everything that matters is overridden.

Given the only consumer is sourcesContent: false, consider either narrowing the public type to a small allowlist or naming the field something closer to its actual purpose. Not blocking, but the surface area will be hard to take back later.

@VaguelySerious

Copy link
Copy Markdown
Member

Seems like tests are still failing, please re-ping me when they pass 👋

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

Hey @VaguelySerious, the related tests are passing 🥳

@vercel

vercelBot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Deployment failed with the following error:

The Preview Deployment Suffix domain "labs.vercel.dev" is not available under the current scope. Learn more: https://vercel.link/3a1

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: no blocking issues

Comment threadpackages/nitro/src/index.ts Outdated
};

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The webhook handler is registered at /.well-known/workflow/v1/webhook/:token (line 214), but this functionRules key uses webhook/**. I built workbench/nitro-v3 with NITRO_PRESET=vercel and workflow.runtime: 'nodejs22.x' and verified that nitro's vercel preset generates two function dirs in this case:

  • webhook/[token].func — created via routeFuncRouter.match against the :token route, gets the runtime override.
  • webhook/[...].func — created from this ** rule, also gets the runtime override.

In config.json, the webhook/(?:.*) route emitted by this rule comes before webhook/(?<token>[^/]+), so [token].func ends up unreachable. The result is a wasted function dir (extra disk/build/deploy size) and slightly looser routing (/webhook/ with no token now matches).

Suggested fix: use the same pattern as the handler, e.g. '/.well-known/workflow/v1/webhook/:token'. With matching keys, nitro reuses the single funcDir under the runtime override.

// V2 combined: a single trigger covers both `__wkf_workflow_*`
// (workflow orchestration) and `__wkf_step_*` (step execution),
// since the same handler dispatches both.
experimentalTriggers: [WORKFLOW_QUEUE_TRIGGER],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The comment two lines above says "merged with any user-defined rules at the same paths so explicit user config (e.g. memory) is preserved", but the spread order here (user first, then maxDuration / experimentalTriggers) means workflow's values always win. Only fields that workflow doesn't touch (like memory) survive — user-set maxDuration, runtime, or additional experimentalTriggers get clobbered.

Two small things:

  1. The comment should match the behavior — something like "workflow's required triggers/maxDuration take precedence; unrelated fields like memory are preserved."
  2. Other integrations (e.g. packages/sveltekit) concatenate user triggers rather than replacing them. Probably not an issue in practice since this is a workflow-owned route, but worth deciding deliberately.

Also (related): also exercised by the E2E Vercel Prod Tests (hono) job, which currently fails on maxRetries=0 disables retries (60s timeout) on this PR while passing on main. And E2E Local Prod Tests (tanstack-start) fails on abortVoidSleepTimeoutWorkflow (also passes on main). Both look like potential regressions worth investigating before merge — they aren't in the known-flaky set.

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';
rules[webhookPath] = { ...rules[webhookPath], runtime };
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

runtime is propagated to the flow and webhook routes but not to the manifest route (/.well-known/workflow/v1/manifest.json, registered via addManifestHandler). If a user picks a non-default runtime, the manifest gets served from the catch-all __server function on the default runtime. Low impact (manifest is static JSON), but inconsistent — worth either documenting why it's intentionally excluded, or applying the same override.

return majorVersion === 2;
}
return !nitro.routing;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

This PR adds non-trivial new logic (isNitroV2, the useLegacyVercelBuild branch, the functionRules merge) with no unit test coverage.

Also heads-up: the existing packages/nitro/src/index.test.ts is already broken on main (2 of 10 tests reference #workflow/steps.mjs, which no longer exists since the V1→V2 combined bundle change). Not introduced by this PR, but since the file is already touching the same area, fixing those tests and adding a couple of cases for isNitroV2 and the v3-vercel branch would be a low-cost addition.

Comment threadpackages/nitro/src/builders.ts Outdated
*/
function getNitroStringExternals(nitro: Nitro): string[] | undefined {
const externals = nitro.options.externals?.external?.filter(
const external = (nitro.options as { externals?: { external?: unknown[] } })

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

The structural cast widens external to unknown[] then filters strings — works, but a named alias would read better:

typeNitroV2Options={externals?: {external?: unknown[]}};constexternal=(nitro.optionsasNitroV2Options).externals?.external;

Non-blocking.

Comment threadworkbench/vite/vite.config.ts
The functionRule was keyed by `webhook/**` while the actual handler is
registered at `webhook/:token`, so nitro v3's vercel preset created a
second `webhook/[...].func` next to the real one and shadowed it in the
generated routes. Use the same `:token` pattern as the handler so the
runtime override applies to the function that actually serves traffic.
Also propagate `workflow.runtime` to the public manifest route for
consistency, clarify the merge-precedence comment, and refresh the unit
tests to cover the v3 functionRules path and the `isNitroV2` detection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Wielander <peter.wielander@vercel.com>
@VaguelySerious

Copy link
Copy Markdown
Member

auto-merge is on, waiting for CI to pass

Comment threadpnpm-workspace.yaml
enhanced-resolve: 5.19.0
esbuild: ^0.27.3
nitro: 3.0.1-alpha.1
nitro: ^3.0.260429-beta

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should nitro on the workbench not be a stable release? 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro v2 is nitropack and Nitro v3 is nitro so I think it should be fine. Most the things target Nitro v3 although it's not got a "stable" release yet till Nuxt v5.

@github-actionsgithub-actionsBot mentioned this pull request May 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for c1242e8 (AI decision).

This commit refactors @workflow/nitro to use Nitro v3's functionRules for workflow routes, a code path that does not exist on stable (stable still uses the legacy VercelBuilder for all Vercel deploys regardless of Nitro version). It also bumps the nitro catalog dependency from 3.0.1-alpha.1 to ^3.0.260429-beta, which is a substantial upstream change tied to features only used on main. The webhook pattern fix called out in the changeset only applies to the v3 functionRules path that isn't present on stable.

To override, re-run the Backport to stable workflow manually via workflow_dispatch with this commit SHA.

Sign up for freeto 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.

3 participants

@RihanArfan@VaguelySerious@pi0
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor(nitro): use nitro v3 functionRules workflow routes - #1575

Merged
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement
May 20, 2026
Merged

refactor(nitro): use nitro v3 functionRules workflow routes#1575
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement

Conversation

@RihanArfan

@RihanArfanRihanArfan commented Apr 1, 2026

Copy link
Copy Markdown
Member

Description

Currently we separately build the Workflows functions for the Vercel Build Output API when deploying a Nitro app to Vercel. This means the Workflows routes aren't part of the Nitro bundle.

This PR changes to now handle the Workflows routes as handlers within Nitro v3. Nitro v2 is untouched.

How did you test your changes?

Locally ran nitro dev in the nitro-v3 workspace and visited the API route POST /api/test-direct-step-call.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR
    • During beta, we only use "patch" mode for changes. Don't tag minor/major versions.
    • Use pnpm changeset --empty if you are changing documentation or workbench apps
  • 🔒 DCO sign-off passes (run git commit --signoff on your commits)
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete

@changeset-bot

changeset-botBot commented Apr 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2c843be

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

This PR includes changesets to release 17 packages
NameType
@workflow/nitroPatch
@workflow/nuxtPatch
workflowPatch
@workflow/aiPatch
@workflow/world-testingPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Comment threadpackages/nitro/src/index.ts
@RihanArfan
RihanArfanforce-pushed the feat/nitro-builder-improvement branch from e312e97 to 3eb081aCompareApril 7, 2026 10:28
@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.030s (-29.7% 🟢)1.006s (~)0.975s101.00x
💻 LocalExpress0.031s (-29.6% 🟢)1.005s (~)0.974s101.03x
🐘 PostgresExpress0.043s (-25.5% 🟢)1.010s (~)0.967s101.43x
🐘 PostgresNitro0.048s (-49.2% 🟢)1.010s (-3.1%)0.962s101.60x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.224s (-10.9% 🟢)1.890s (-19.0% 🟢)1.666s101.00x
▲ VercelNitro0.225s (-45.0% 🟢)2.732s (+8.9% 🔺)2.507s101.01x
▲ VercelExpress0.397s (+68.6% 🔺)2.027s (-5.1% 🟢)1.630s101.77x

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

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.072s (-5.3% 🟢)2.007s (~)0.935s101.00x
💻 LocalExpress1.075s (-4.5%)2.006s (~)0.931s101.00x
🐘 PostgresExpress1.082s (-5.6% 🟢)2.009s (~)0.927s101.01x
🐘 PostgresNitro1.083s (-5.0%)2.010s (~)0.927s101.01x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.539s (-17.9% 🟢)3.295s (-13.5% 🟢)1.756s101.00x
▲ VercelNext.js (Turbopack)1.689s (-17.0% 🟢)4.668s (+21.9% 🔺)2.979s101.10x
▲ VercelNitro2.027s (-47.9% 🟢)3.895s (-34.1% 🟢)1.869s101.32x

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

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.412s (-4.7%)11.021s (~)0.608s31.00x
🐘 PostgresExpress10.416s (-5.0%)11.016s (~)0.600s31.00x
💻 LocalNitro10.424s (-4.8%)11.021s (~)0.598s31.00x
🐘 PostgresNitro10.433s (-4.0%)11.020s (~)0.586s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)13.515s (-22.0% 🟢)15.908s (-18.0% 🟢)2.392s21.00x
▲ VercelExpress13.568s (-20.1% 🟢)15.557s (-22.3% 🟢)1.989s21.00x
▲ VercelNitro13.587s (-42.7% 🟢)15.559s (-38.1% 🟢)1.972s21.01x

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

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.456s (-7.7% 🟢)14.017s (-6.7% 🟢)0.561s51.00x
💻 LocalExpress13.459s (-10.1% 🟢)14.025s (-6.7% 🟢)0.566s51.00x
🐘 PostgresNitro13.466s (-7.7% 🟢)14.019s (-6.7% 🟢)0.553s51.00x
💻 LocalNitro13.479s (-10.5% 🟢)14.027s (-12.5% 🟢)0.548s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express20.282s (-59.7% 🟢)22.320s (-57.5% 🟢)2.038s31.00x
▲ VercelNext.js (Turbopack)20.648s (-60.7% 🟢)22.415s (-59.0% 🟢)1.768s31.02x
▲ VercelNitro20.776s (-67.8% 🟢)22.345s (-66.5% 🟢)1.569s31.02x

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

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express11.896s (-28.3% 🟢)12.020s (-29.4% 🟢)0.124s81.00x
💻 LocalNitro11.919s (-29.0% 🟢)12.022s (-29.4% 🟢)0.103s81.00x
🐘 PostgresExpress11.979s (-14.5% 🟢)12.268s (-15.9% 🟢)0.288s81.01x
🐘 PostgresNitro12.158s (-12.9% 🟢)12.872s (-10.0% 🟢)0.714s71.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express30.115s (-75.2% 🟢)32.130s (-74.0% 🟢)2.014s31.00x
▲ VercelNitro30.529s (-92.8% 🟢)32.167s (-92.4% 🟢)1.638s31.01x
▲ VercelNext.js (Turbopack)31.252s (-92.1% 🟢)32.624s (-91.7% 🟢)1.372s31.04x

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

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.138s (-9.7% 🟢)2.007s (~)0.870s151.00x
💻 LocalExpress1.166s (-21.7% 🟢)2.006s (~)0.840s151.02x
💻 LocalNitro1.175s (-28.0% 🟢)2.006s (-3.3%)0.830s151.03x
🐘 PostgresNitro1.238s (-2.9%)2.074s (+3.2%)0.836s151.09x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.605s (-7.5% 🟢)3.989s (-7.7% 🟢)1.383s91.00x
▲ VercelExpress2.632s (-8.0% 🟢)4.521s (-2.2%)1.889s71.01x
▲ VercelNext.js (Turbopack)2.660s (-21.7% 🟢)4.213s (-14.6% 🟢)1.553s81.02x

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

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.199s (-49.2% 🟢)2.006s (-33.3% 🟢)0.807s151.00x
🐘 PostgresNitro1.234s (-47.5% 🟢)2.008s (-33.3% 🟢)0.773s151.03x
💻 LocalExpress1.611s (-45.5% 🟢)2.006s (-41.9% 🟢)0.395s151.34x
💻 LocalNitro1.781s (-43.3% 🟢)2.005s (-48.4% 🟢)0.224s151.49x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.495s (-3.4%)5.288s (+3.5%)1.793s61.00x
▲ VercelNext.js (Turbopack)3.647s (-48.6% 🟢)5.434s (-39.0% 🟢)1.787s61.04x
▲ VercelNitro4.003s (-1.2%)5.826s (-1.6%)1.823s61.15x

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

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.290s (-63.0% 🟢)2.007s (-50.0% 🟢)0.717s151.00x
🐘 PostgresNitro1.350s (-61.2% 🟢)2.008s (-49.9% 🟢)0.659s151.05x
💻 LocalExpress4.222s (-49.4% 🟢)4.582s (-49.2% 🟢)0.361s73.27x
💻 LocalNitro5.125s (-38.6% 🟢)5.678s (-37.1% 🟢)0.554s63.97x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.678s (+32.7% 🔺)6.756s (+22.1% 🔺)2.078s51.00x
▲ VercelExpress6.267s (+47.8% 🔺)8.267s (+34.9% 🔺)2.000s41.34x
▲ VercelNext.js (Turbopack)7.564s (-15.2% 🟢)9.835s (-10.3% 🟢)2.271s41.62x

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

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.145s (-8.9% 🟢)2.008s (~)0.863s151.00x
🐘 PostgresNitro1.149s (-8.6% 🟢)2.008s (~)0.859s151.00x
💻 LocalExpress1.361s (-28.1% 🟢)2.006s (-15.1% 🟢)0.645s151.19x
💻 LocalNitro1.371s (-26.5% 🟢)2.006s (-14.3% 🟢)0.635s151.20x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.498s (+1.6%)4.099s (-1.7%)1.601s81.00x
▲ VercelNext.js (Turbopack)2.715s (-7.4% 🟢)4.421s (-4.8%)1.707s71.09x
▲ VercelExpress2.897s (+12.2% 🔺)4.519s (+3.9%)1.622s71.16x

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

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.192s (-49.1% 🟢)2.008s (-33.3% 🟢)0.817s151.00x
🐘 PostgresExpress1.198s (-48.8% 🟢)2.009s (-33.3% 🟢)0.811s151.01x
💻 LocalExpress1.702s (-45.7% 🟢)2.008s (-46.6% 🟢)0.306s151.43x
💻 LocalNitro2.025s (-33.9% 🟢)2.391s (-38.5% 🟢)0.366s131.70x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.357s (+3.8%)5.248s (+3.4%)1.892s61.00x
▲ VercelExpress3.634s (+13.8% 🔺)5.286s (+10.3% 🔺)1.652s61.08x
▲ VercelNext.js (Turbopack)4.356s (+38.6% 🔺)6.224s (+37.6% 🔺)1.868s61.30x

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

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.271s (-63.7% 🟢)2.007s (-50.0% 🟢)0.735s151.00x
🐘 PostgresNitro1.304s (-62.5% 🟢)2.007s (-49.9% 🟢)0.703s151.03x
💻 LocalExpress4.961s (-43.6% 🟢)5.514s (-40.5% 🟢)0.553s63.90x
💻 LocalNitro6.189s (-32.3% 🟢)6.415s (-36.0% 🟢)0.226s54.87x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.914s (-3.5%)6.839s (~)1.925s51.00x
▲ VercelExpress5.278s (-17.7% 🟢)7.562s (-7.5% 🟢)2.284s41.07x
▲ VercelNext.js (Turbopack)6.788s (~)8.497s (-0.5%)1.709s41.38x

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

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.434s (-48.2% 🟢)1.006s (-1.6%)0.572s601.00x
🐘 PostgresNitro0.454s (-44.7% 🟢)1.024s (+1.8%)0.570s591.05x
💻 LocalNitro0.485s (-50.5% 🟢)1.004s (-8.2% 🟢)0.519s601.12x
💻 LocalExpress0.563s (-42.7% 🟢)1.092s (+1.5%)0.528s571.30x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.763s (-78.4% 🟢)6.470s (-73.1% 🟢)1.707s101.00x
▲ VercelNext.js (Turbopack)5.279s (-63.6% 🟢)6.979s (-56.6% 🟢)1.700s91.11x
▲ VercelExpress5.386s (-71.7% 🟢)7.590s (-64.4% 🟢)2.204s81.13x

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

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.038s (-46.1% 🟢)1.791s (-14.7% 🟢)0.753s511.00x
🐘 PostgresExpress1.094s (-44.7% 🟢)1.705s (-24.5% 🟢)0.611s531.05x
💻 LocalExpress1.163s (-61.4% 🟢)2.005s (-44.1% 🟢)0.842s451.12x
💻 LocalNitro1.200s (-60.5% 🟢)2.006s (-46.6% 🟢)0.806s451.16x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.272s (-68.9% 🟢)14.115s (-65.8% 🟢)1.842s71.00x
▲ VercelNext.js (Turbopack)13.606s (-72.7% 🟢)15.509s (-70.0% 🟢)1.903s61.11x
▲ VercelExpress13.810s (-60.0% 🟢)16.683s (-54.7% 🟢)2.872s61.13x

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

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.047s (-48.7% 🟢)2.506s (-42.6% 🟢)0.459s481.00x
🐘 PostgresNitro2.132s (-48.0% 🟢)2.798s (-39.2% 🟢)0.666s431.04x
💻 LocalExpress2.603s (-71.7% 🟢)3.007s (-70.0% 🟢)0.404s401.27x
💻 LocalNitro2.699s (-71.0% 🟢)3.008s (-70.0% 🟢)0.308s401.32x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro39.315s (-59.4% 🟢)41.156s (-58.2% 🟢)1.841s31.00x
▲ VercelExpress40.781s (-68.6% 🟢)43.325s (-67.2% 🟢)2.544s31.04x
▲ VercelNext.js (Turbopack)41.245s (-61.5% 🟢)42.926s (-60.6% 🟢)1.681s31.05x

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

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.165s (-41.6% 🟢)1.006s (~)0.841s601.00x
🐘 PostgresNitro0.172s (-39.4% 🟢)1.006s (~)0.834s601.04x
💻 LocalExpress0.421s (-24.9% 🟢)1.004s (~)0.583s602.55x
💻 LocalNitro0.463s (-23.5% 🟢)1.004s (-1.7%)0.541s602.81x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.161s (+10.6% 🔺)4.409s (+21.2% 🔺)2.249s141.00x
▲ VercelNext.js (Turbopack)2.599s (+28.5% 🔺)4.308s (+13.6% 🔺)1.709s141.20x
▲ VercelNitro2.656s (+59.9% 🔺)4.513s (+34.7% 🔺)1.858s141.23x

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

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.285s (-44.0% 🟢)1.006s (~)0.720s901.00x
🐘 PostgresNitro0.298s (-40.0% 🟢)1.006s (~)0.708s901.04x
💻 LocalExpress2.156s (-14.2% 🟢)2.685s (-10.8% 🟢)0.529s347.56x
💻 LocalNitro2.179s (-14.2% 🟢)2.912s (-3.2%)0.733s317.64x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)8.136s (+130.1% 🔺)10.219s (+96.8% 🔺)2.083s91.00x
▲ VercelExpress8.249s (+170.8% 🔺)10.683s (+122.2% 🔺)2.434s91.01x
▲ VercelNitro9.696s (+200.6% 🔺)11.640s (+141.4% 🔺)1.944s81.19x

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

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.558s (-31.8% 🟢)1.005s (-1.2%)0.447s1201.00x
🐘 PostgresNitro0.652s (-17.5% 🟢)1.006s (~)0.354s1201.17x
💻 LocalExpress9.153s (-18.2% 🟢)9.795s (-18.0% 🟢)0.643s1316.40x
💻 LocalNitro10.117s (-9.6% 🟢)10.778s (-7.6% 🟢)0.661s1218.13x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.676s (+119.6% 🔺)24.944s (+103.0% 🔺)2.268s51.00x
▲ VercelNitro23.736s (+207.4% 🔺)25.097s (+167.0% 🔺)1.361s51.05x
▲ VercelExpress28.260s (+280.9% 🔺)30.473s (+229.6% 🔺)2.213s41.25x

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

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.126s (+465.4% 🔺)2.004s (+99.5% 🔺)0.010s (-19.8% 🟢)2.016s (+98.0% 🔺)0.890s101.00x
🐘 PostgresExpress1.133s (+452.5% 🔺)2.002s (+100.5% 🔺)0.001s (-25.0% 🟢)2.010s (+98.8% 🔺)0.877s101.01x
💻 LocalNitro1.143s (+434.8% 🔺)2.005s (+99.6% 🔺)0.012s (-2.4%)2.019s (+98.2% 🔺)0.877s101.02x
🐘 PostgresNitro1.157s (+464.4% 🔺)1.999s (+100.0% 🔺)0.001s (-20.0% 🟢)2.010s (+98.8% 🔺)0.853s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)2.185s (-68.1% 🟢)3.165s (-63.4% 🟢)1.925s (+204.7% 🔺)5.496s (-43.9% 🟢)3.311s101.00x
▲ VercelNitro2.295s (-40.1% 🟢)3.603s (-31.7% 🟢)13.820s (+1762.3% 🔺)17.886s (+175.9% 🔺)15.591s101.05x
▲ VercelExpress2.329s (-7.0% 🟢)3.832s (-6.3% 🟢)1.518s (+58.0% 🔺)5.905s (+5.6% 🔺)3.576s101.07x

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

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.490s (+136.5% 🔺)2.005s (+99.2% 🔺)0.003s (-12.1% 🟢)2.022s (+97.7% 🔺)0.532s301.00x
🐘 PostgresNitro1.528s (+144.8% 🔺)2.003s (+98.9% 🔺)0.004s (-3.3%)2.027s (+98.2% 🔺)0.499s301.03x
💻 LocalNitro1.707s (+103.5% 🔺)2.011s (+98.7% 🔺)0.010s (+6.7% 🔺)2.202s (+97.3% 🔺)0.495s281.15x
💻 LocalExpress1.877s (+148.0% 🔺)2.011s (+95.5% 🔺)0.009s (-0.9%)2.394s (+130.2% 🔺)0.517s271.26x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.935s (-8.8% 🟢)7.705s (-3.8%)0.216s (-47.0% 🟢)8.447s (-4.4%)2.512s81.00x
▲ VercelNitro5.975s (-79.7% 🟢)7.548s (-75.5% 🟢)0.263s (+134.7% 🔺)8.277s (-74.0% 🟢)2.302s81.01x
▲ VercelNext.js (Turbopack)6.750s (-60.1% 🟢)7.706s (-57.8% 🟢)0.297s (+40.7% 🔺)8.648s (-54.3% 🟢)1.898s71.14x

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

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.637s (-34.3% 🟢)1.034s (-17.1% 🟢)0.000s (-58.6% 🟢)1.042s (-17.2% 🟢)0.405s581.00x
🐘 PostgresExpress0.647s (-32.7% 🟢)1.050s (-17.8% 🟢)0.000s (+58.6% 🔺)1.060s (-18.8% 🟢)0.413s581.02x
💻 LocalExpress1.339s (+9.3% 🔺)2.016s (~)0.001s (+70.0% 🔺)2.018s (~)0.679s302.10x
💻 LocalNitro1.726s (+41.2% 🔺)2.015s (~)0.000s (+140.0% 🔺)2.418s (+19.6% 🔺)0.691s252.71x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)3.534s (-65.3% 🟢)4.591s (-60.1% 🟢)0.000s (NaN%)5.094s (-57.7% 🟢)1.560s121.00x
▲ VercelNitro3.538s (+16.0% 🔺)4.997s (+13.8% 🔺)0.000s (+225.0% 🔺)5.453s (+13.4% 🔺)1.916s121.00x
▲ VercelExpress3.646s (-2.5%)5.083s (~)0.000s (~)5.696s (+3.0%)2.050s111.03x

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

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.294s (-27.0% 🟢)1.905s (-12.5% 🟢)0.000s (NaN%)1.924s (-12.5% 🟢)0.630s321.00x
🐘 PostgresNitro1.320s (-26.3% 🟢)2.031s (-5.1% 🟢)0.000s (-100.0% 🟢)2.043s (-6.0% 🟢)0.723s301.02x
💻 LocalExpress3.031s (-12.6% 🟢)3.777s (-6.4% 🟢)0.001s (-21.9% 🟢)3.780s (-6.3% 🟢)0.749s162.34x
💻 LocalNitro3.076s (-9.2% 🟢)3.777s (-6.3% 🟢)0.001s (-6.2% 🟢)3.779s (-6.4% 🟢)0.703s162.38x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.447s (+33.1% 🔺)7.047s (+31.1% 🔺)0.000s (-54.2% 🟢)7.517s (+29.7% 🔺)2.070s81.00x
▲ VercelNext.js (Turbopack)5.517s (-1.8%)7.043s (+0.9%)0.000s (~)7.537s (~)2.020s81.01x
▲ VercelExpress6.217s (+35.5% 🔺)7.655s (+27.1% 🔺)0.000s (NaN%)8.501s (+31.7% 🔺)2.284s81.14x

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

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress17/21
🐘 PostgresExpress18/21
▲ VercelNitro9/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)▲ Vercel21/21
Nitro🐘 Postgres15/21
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)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production120002191419
✅ 💻 Local Development158702191806
✅ 📦 Local Production158702191806
✅ 🐘 Local Postgres158702191806
✅ 🪟 Windows12900129
✅ 📋 Other7270176903
Total6817010527869

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro103026
✅ example103026
✅ express103026
✅ fastify103026
✅ hono103026
✅ nextjs-turbopack12702
✅ nextjs-webpack12702
✅ nitro103026
✅ nuxt103026
✅ sveltekit12207
✅ vite103026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack12900
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable104025
✅ e2e-local-dev-tanstack-start-104025
✅ e2e-local-postgres-nest-stable104025
✅ e2e-local-postgres-tanstack-start-104025
✅ e2e-local-prod-nest-stable104025
✅ e2e-local-prod-tanstack-start-104025
✅ e2e-vercel-prod-tanstack-start103026

📋 View full workflow run

Comment threadpackages/nitro/src/index.ts Outdated
@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably know better than me whether the nits apply, so use your judgement there. Happy to approve this once the e2e tests are running again and CI is green

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: blocking issues found

Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.test.ts
Comment threadpackages/builders/src/types.ts Outdated
* Extra esbuild options merged into every bundle this builder produces
* (steps, intermediate workflow, final workflow wrapper, webhook).
*/
esbuildOptions?: Partial<BuildOptions>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

esbuildOptions: Partial<BuildOptions> is a broad surface for one knob

The new field accepts the full esbuild BuildOptions and is spread into four bundle calls with very different needs (steps: node/esm, workflow VM: neutral/cjs, webhook: node/esm, final wrapper: node/esm). The current spread order puts user options first so builder-required options like platform, format, bundle, plugins win — that's defensive — but the API still implies "you can pass any esbuild option" when in practice almost everything that matters is overridden.

Given the only consumer is sourcesContent: false, consider either narrowing the public type to a small allowlist or naming the field something closer to its actual purpose. Not blocking, but the surface area will be hard to take back later.

@VaguelySerious

Copy link
Copy Markdown
Member

Seems like tests are still failing, please re-ping me when they pass 👋

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

Hey @VaguelySerious, the related tests are passing 🥳

@vercel

vercelBot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Deployment failed with the following error:

The Preview Deployment Suffix domain "labs.vercel.dev" is not available under the current scope. Learn more: https://vercel.link/3a1

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: no blocking issues

Comment threadpackages/nitro/src/index.ts Outdated
};

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The webhook handler is registered at /.well-known/workflow/v1/webhook/:token (line 214), but this functionRules key uses webhook/**. I built workbench/nitro-v3 with NITRO_PRESET=vercel and workflow.runtime: 'nodejs22.x' and verified that nitro's vercel preset generates two function dirs in this case:

  • webhook/[token].func — created via routeFuncRouter.match against the :token route, gets the runtime override.
  • webhook/[...].func — created from this ** rule, also gets the runtime override.

In config.json, the webhook/(?:.*) route emitted by this rule comes before webhook/(?<token>[^/]+), so [token].func ends up unreachable. The result is a wasted function dir (extra disk/build/deploy size) and slightly looser routing (/webhook/ with no token now matches).

Suggested fix: use the same pattern as the handler, e.g. '/.well-known/workflow/v1/webhook/:token'. With matching keys, nitro reuses the single funcDir under the runtime override.

// V2 combined: a single trigger covers both `__wkf_workflow_*`
// (workflow orchestration) and `__wkf_step_*` (step execution),
// since the same handler dispatches both.
experimentalTriggers: [WORKFLOW_QUEUE_TRIGGER],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The comment two lines above says "merged with any user-defined rules at the same paths so explicit user config (e.g. memory) is preserved", but the spread order here (user first, then maxDuration / experimentalTriggers) means workflow's values always win. Only fields that workflow doesn't touch (like memory) survive — user-set maxDuration, runtime, or additional experimentalTriggers get clobbered.

Two small things:

  1. The comment should match the behavior — something like "workflow's required triggers/maxDuration take precedence; unrelated fields like memory are preserved."
  2. Other integrations (e.g. packages/sveltekit) concatenate user triggers rather than replacing them. Probably not an issue in practice since this is a workflow-owned route, but worth deciding deliberately.

Also (related): also exercised by the E2E Vercel Prod Tests (hono) job, which currently fails on maxRetries=0 disables retries (60s timeout) on this PR while passing on main. And E2E Local Prod Tests (tanstack-start) fails on abortVoidSleepTimeoutWorkflow (also passes on main). Both look like potential regressions worth investigating before merge — they aren't in the known-flaky set.

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';
rules[webhookPath] = { ...rules[webhookPath], runtime };
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

runtime is propagated to the flow and webhook routes but not to the manifest route (/.well-known/workflow/v1/manifest.json, registered via addManifestHandler). If a user picks a non-default runtime, the manifest gets served from the catch-all __server function on the default runtime. Low impact (manifest is static JSON), but inconsistent — worth either documenting why it's intentionally excluded, or applying the same override.

return majorVersion === 2;
}
return !nitro.routing;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

This PR adds non-trivial new logic (isNitroV2, the useLegacyVercelBuild branch, the functionRules merge) with no unit test coverage.

Also heads-up: the existing packages/nitro/src/index.test.ts is already broken on main (2 of 10 tests reference #workflow/steps.mjs, which no longer exists since the V1→V2 combined bundle change). Not introduced by this PR, but since the file is already touching the same area, fixing those tests and adding a couple of cases for isNitroV2 and the v3-vercel branch would be a low-cost addition.

Comment threadpackages/nitro/src/builders.ts Outdated
*/
function getNitroStringExternals(nitro: Nitro): string[] | undefined {
const externals = nitro.options.externals?.external?.filter(
const external = (nitro.options as { externals?: { external?: unknown[] } })

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

The structural cast widens external to unknown[] then filters strings — works, but a named alias would read better:

typeNitroV2Options={externals?: {external?: unknown[]}};constexternal=(nitro.optionsasNitroV2Options).externals?.external;

Non-blocking.

Comment threadworkbench/vite/vite.config.ts
The functionRule was keyed by `webhook/**` while the actual handler is
registered at `webhook/:token`, so nitro v3's vercel preset created a
second `webhook/[...].func` next to the real one and shadowed it in the
generated routes. Use the same `:token` pattern as the handler so the
runtime override applies to the function that actually serves traffic.
Also propagate `workflow.runtime` to the public manifest route for
consistency, clarify the merge-precedence comment, and refresh the unit
tests to cover the v3 functionRules path and the `isNitroV2` detection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Wielander <peter.wielander@vercel.com>
@VaguelySerious

Copy link
Copy Markdown
Member

auto-merge is on, waiting for CI to pass

Comment threadpnpm-workspace.yaml
enhanced-resolve: 5.19.0
esbuild: ^0.27.3
nitro: 3.0.1-alpha.1
nitro: ^3.0.260429-beta

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should nitro on the workbench not be a stable release? 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro v2 is nitropack and Nitro v3 is nitro so I think it should be fine. Most the things target Nitro v3 although it's not got a "stable" release yet till Nuxt v5.

@github-actionsgithub-actionsBot mentioned this pull request May 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for c1242e8 (AI decision).

This commit refactors @workflow/nitro to use Nitro v3's functionRules for workflow routes, a code path that does not exist on stable (stable still uses the legacy VercelBuilder for all Vercel deploys regardless of Nitro version). It also bumps the nitro catalog dependency from 3.0.1-alpha.1 to ^3.0.260429-beta, which is a substantial upstream change tied to features only used on main. The webhook pattern fix called out in the changeset only applies to the v3 functionRules path that isn't present on stable.

To override, re-run the Backport to stable workflow manually via workflow_dispatch with this commit SHA.

Sign up for freeto 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.

3 participants

@RihanArfan@VaguelySerious@pi0
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor(nitro): use nitro v3 functionRules workflow routes - #1575

Merged
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement
May 20, 2026
Merged

refactor(nitro): use nitro v3 functionRules workflow routes#1575
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement

Conversation

@RihanArfan

@RihanArfanRihanArfan commented Apr 1, 2026

Copy link
Copy Markdown
Member

Description

Currently we separately build the Workflows functions for the Vercel Build Output API when deploying a Nitro app to Vercel. This means the Workflows routes aren't part of the Nitro bundle.

This PR changes to now handle the Workflows routes as handlers within Nitro v3. Nitro v2 is untouched.

How did you test your changes?

Locally ran nitro dev in the nitro-v3 workspace and visited the API route POST /api/test-direct-step-call.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR
    • During beta, we only use "patch" mode for changes. Don't tag minor/major versions.
    • Use pnpm changeset --empty if you are changing documentation or workbench apps
  • 🔒 DCO sign-off passes (run git commit --signoff on your commits)
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete

@changeset-bot

changeset-botBot commented Apr 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2c843be

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

This PR includes changesets to release 17 packages
NameType
@workflow/nitroPatch
@workflow/nuxtPatch
workflowPatch
@workflow/aiPatch
@workflow/world-testingPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Comment threadpackages/nitro/src/index.ts
@RihanArfan
RihanArfanforce-pushed the feat/nitro-builder-improvement branch from e312e97 to 3eb081aCompareApril 7, 2026 10:28
@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.030s (-29.7% 🟢)1.006s (~)0.975s101.00x
💻 LocalExpress0.031s (-29.6% 🟢)1.005s (~)0.974s101.03x
🐘 PostgresExpress0.043s (-25.5% 🟢)1.010s (~)0.967s101.43x
🐘 PostgresNitro0.048s (-49.2% 🟢)1.010s (-3.1%)0.962s101.60x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.224s (-10.9% 🟢)1.890s (-19.0% 🟢)1.666s101.00x
▲ VercelNitro0.225s (-45.0% 🟢)2.732s (+8.9% 🔺)2.507s101.01x
▲ VercelExpress0.397s (+68.6% 🔺)2.027s (-5.1% 🟢)1.630s101.77x

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

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.072s (-5.3% 🟢)2.007s (~)0.935s101.00x
💻 LocalExpress1.075s (-4.5%)2.006s (~)0.931s101.00x
🐘 PostgresExpress1.082s (-5.6% 🟢)2.009s (~)0.927s101.01x
🐘 PostgresNitro1.083s (-5.0%)2.010s (~)0.927s101.01x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.539s (-17.9% 🟢)3.295s (-13.5% 🟢)1.756s101.00x
▲ VercelNext.js (Turbopack)1.689s (-17.0% 🟢)4.668s (+21.9% 🔺)2.979s101.10x
▲ VercelNitro2.027s (-47.9% 🟢)3.895s (-34.1% 🟢)1.869s101.32x

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

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.412s (-4.7%)11.021s (~)0.608s31.00x
🐘 PostgresExpress10.416s (-5.0%)11.016s (~)0.600s31.00x
💻 LocalNitro10.424s (-4.8%)11.021s (~)0.598s31.00x
🐘 PostgresNitro10.433s (-4.0%)11.020s (~)0.586s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)13.515s (-22.0% 🟢)15.908s (-18.0% 🟢)2.392s21.00x
▲ VercelExpress13.568s (-20.1% 🟢)15.557s (-22.3% 🟢)1.989s21.00x
▲ VercelNitro13.587s (-42.7% 🟢)15.559s (-38.1% 🟢)1.972s21.01x

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

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.456s (-7.7% 🟢)14.017s (-6.7% 🟢)0.561s51.00x
💻 LocalExpress13.459s (-10.1% 🟢)14.025s (-6.7% 🟢)0.566s51.00x
🐘 PostgresNitro13.466s (-7.7% 🟢)14.019s (-6.7% 🟢)0.553s51.00x
💻 LocalNitro13.479s (-10.5% 🟢)14.027s (-12.5% 🟢)0.548s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express20.282s (-59.7% 🟢)22.320s (-57.5% 🟢)2.038s31.00x
▲ VercelNext.js (Turbopack)20.648s (-60.7% 🟢)22.415s (-59.0% 🟢)1.768s31.02x
▲ VercelNitro20.776s (-67.8% 🟢)22.345s (-66.5% 🟢)1.569s31.02x

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

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express11.896s (-28.3% 🟢)12.020s (-29.4% 🟢)0.124s81.00x
💻 LocalNitro11.919s (-29.0% 🟢)12.022s (-29.4% 🟢)0.103s81.00x
🐘 PostgresExpress11.979s (-14.5% 🟢)12.268s (-15.9% 🟢)0.288s81.01x
🐘 PostgresNitro12.158s (-12.9% 🟢)12.872s (-10.0% 🟢)0.714s71.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express30.115s (-75.2% 🟢)32.130s (-74.0% 🟢)2.014s31.00x
▲ VercelNitro30.529s (-92.8% 🟢)32.167s (-92.4% 🟢)1.638s31.01x
▲ VercelNext.js (Turbopack)31.252s (-92.1% 🟢)32.624s (-91.7% 🟢)1.372s31.04x

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

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.138s (-9.7% 🟢)2.007s (~)0.870s151.00x
💻 LocalExpress1.166s (-21.7% 🟢)2.006s (~)0.840s151.02x
💻 LocalNitro1.175s (-28.0% 🟢)2.006s (-3.3%)0.830s151.03x
🐘 PostgresNitro1.238s (-2.9%)2.074s (+3.2%)0.836s151.09x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.605s (-7.5% 🟢)3.989s (-7.7% 🟢)1.383s91.00x
▲ VercelExpress2.632s (-8.0% 🟢)4.521s (-2.2%)1.889s71.01x
▲ VercelNext.js (Turbopack)2.660s (-21.7% 🟢)4.213s (-14.6% 🟢)1.553s81.02x

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

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.199s (-49.2% 🟢)2.006s (-33.3% 🟢)0.807s151.00x
🐘 PostgresNitro1.234s (-47.5% 🟢)2.008s (-33.3% 🟢)0.773s151.03x
💻 LocalExpress1.611s (-45.5% 🟢)2.006s (-41.9% 🟢)0.395s151.34x
💻 LocalNitro1.781s (-43.3% 🟢)2.005s (-48.4% 🟢)0.224s151.49x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.495s (-3.4%)5.288s (+3.5%)1.793s61.00x
▲ VercelNext.js (Turbopack)3.647s (-48.6% 🟢)5.434s (-39.0% 🟢)1.787s61.04x
▲ VercelNitro4.003s (-1.2%)5.826s (-1.6%)1.823s61.15x

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

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.290s (-63.0% 🟢)2.007s (-50.0% 🟢)0.717s151.00x
🐘 PostgresNitro1.350s (-61.2% 🟢)2.008s (-49.9% 🟢)0.659s151.05x
💻 LocalExpress4.222s (-49.4% 🟢)4.582s (-49.2% 🟢)0.361s73.27x
💻 LocalNitro5.125s (-38.6% 🟢)5.678s (-37.1% 🟢)0.554s63.97x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.678s (+32.7% 🔺)6.756s (+22.1% 🔺)2.078s51.00x
▲ VercelExpress6.267s (+47.8% 🔺)8.267s (+34.9% 🔺)2.000s41.34x
▲ VercelNext.js (Turbopack)7.564s (-15.2% 🟢)9.835s (-10.3% 🟢)2.271s41.62x

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

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.145s (-8.9% 🟢)2.008s (~)0.863s151.00x
🐘 PostgresNitro1.149s (-8.6% 🟢)2.008s (~)0.859s151.00x
💻 LocalExpress1.361s (-28.1% 🟢)2.006s (-15.1% 🟢)0.645s151.19x
💻 LocalNitro1.371s (-26.5% 🟢)2.006s (-14.3% 🟢)0.635s151.20x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.498s (+1.6%)4.099s (-1.7%)1.601s81.00x
▲ VercelNext.js (Turbopack)2.715s (-7.4% 🟢)4.421s (-4.8%)1.707s71.09x
▲ VercelExpress2.897s (+12.2% 🔺)4.519s (+3.9%)1.622s71.16x

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

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.192s (-49.1% 🟢)2.008s (-33.3% 🟢)0.817s151.00x
🐘 PostgresExpress1.198s (-48.8% 🟢)2.009s (-33.3% 🟢)0.811s151.01x
💻 LocalExpress1.702s (-45.7% 🟢)2.008s (-46.6% 🟢)0.306s151.43x
💻 LocalNitro2.025s (-33.9% 🟢)2.391s (-38.5% 🟢)0.366s131.70x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.357s (+3.8%)5.248s (+3.4%)1.892s61.00x
▲ VercelExpress3.634s (+13.8% 🔺)5.286s (+10.3% 🔺)1.652s61.08x
▲ VercelNext.js (Turbopack)4.356s (+38.6% 🔺)6.224s (+37.6% 🔺)1.868s61.30x

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

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.271s (-63.7% 🟢)2.007s (-50.0% 🟢)0.735s151.00x
🐘 PostgresNitro1.304s (-62.5% 🟢)2.007s (-49.9% 🟢)0.703s151.03x
💻 LocalExpress4.961s (-43.6% 🟢)5.514s (-40.5% 🟢)0.553s63.90x
💻 LocalNitro6.189s (-32.3% 🟢)6.415s (-36.0% 🟢)0.226s54.87x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.914s (-3.5%)6.839s (~)1.925s51.00x
▲ VercelExpress5.278s (-17.7% 🟢)7.562s (-7.5% 🟢)2.284s41.07x
▲ VercelNext.js (Turbopack)6.788s (~)8.497s (-0.5%)1.709s41.38x

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

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.434s (-48.2% 🟢)1.006s (-1.6%)0.572s601.00x
🐘 PostgresNitro0.454s (-44.7% 🟢)1.024s (+1.8%)0.570s591.05x
💻 LocalNitro0.485s (-50.5% 🟢)1.004s (-8.2% 🟢)0.519s601.12x
💻 LocalExpress0.563s (-42.7% 🟢)1.092s (+1.5%)0.528s571.30x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.763s (-78.4% 🟢)6.470s (-73.1% 🟢)1.707s101.00x
▲ VercelNext.js (Turbopack)5.279s (-63.6% 🟢)6.979s (-56.6% 🟢)1.700s91.11x
▲ VercelExpress5.386s (-71.7% 🟢)7.590s (-64.4% 🟢)2.204s81.13x

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

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.038s (-46.1% 🟢)1.791s (-14.7% 🟢)0.753s511.00x
🐘 PostgresExpress1.094s (-44.7% 🟢)1.705s (-24.5% 🟢)0.611s531.05x
💻 LocalExpress1.163s (-61.4% 🟢)2.005s (-44.1% 🟢)0.842s451.12x
💻 LocalNitro1.200s (-60.5% 🟢)2.006s (-46.6% 🟢)0.806s451.16x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.272s (-68.9% 🟢)14.115s (-65.8% 🟢)1.842s71.00x
▲ VercelNext.js (Turbopack)13.606s (-72.7% 🟢)15.509s (-70.0% 🟢)1.903s61.11x
▲ VercelExpress13.810s (-60.0% 🟢)16.683s (-54.7% 🟢)2.872s61.13x

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

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.047s (-48.7% 🟢)2.506s (-42.6% 🟢)0.459s481.00x
🐘 PostgresNitro2.132s (-48.0% 🟢)2.798s (-39.2% 🟢)0.666s431.04x
💻 LocalExpress2.603s (-71.7% 🟢)3.007s (-70.0% 🟢)0.404s401.27x
💻 LocalNitro2.699s (-71.0% 🟢)3.008s (-70.0% 🟢)0.308s401.32x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro39.315s (-59.4% 🟢)41.156s (-58.2% 🟢)1.841s31.00x
▲ VercelExpress40.781s (-68.6% 🟢)43.325s (-67.2% 🟢)2.544s31.04x
▲ VercelNext.js (Turbopack)41.245s (-61.5% 🟢)42.926s (-60.6% 🟢)1.681s31.05x

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

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.165s (-41.6% 🟢)1.006s (~)0.841s601.00x
🐘 PostgresNitro0.172s (-39.4% 🟢)1.006s (~)0.834s601.04x
💻 LocalExpress0.421s (-24.9% 🟢)1.004s (~)0.583s602.55x
💻 LocalNitro0.463s (-23.5% 🟢)1.004s (-1.7%)0.541s602.81x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.161s (+10.6% 🔺)4.409s (+21.2% 🔺)2.249s141.00x
▲ VercelNext.js (Turbopack)2.599s (+28.5% 🔺)4.308s (+13.6% 🔺)1.709s141.20x
▲ VercelNitro2.656s (+59.9% 🔺)4.513s (+34.7% 🔺)1.858s141.23x

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

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.285s (-44.0% 🟢)1.006s (~)0.720s901.00x
🐘 PostgresNitro0.298s (-40.0% 🟢)1.006s (~)0.708s901.04x
💻 LocalExpress2.156s (-14.2% 🟢)2.685s (-10.8% 🟢)0.529s347.56x
💻 LocalNitro2.179s (-14.2% 🟢)2.912s (-3.2%)0.733s317.64x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)8.136s (+130.1% 🔺)10.219s (+96.8% 🔺)2.083s91.00x
▲ VercelExpress8.249s (+170.8% 🔺)10.683s (+122.2% 🔺)2.434s91.01x
▲ VercelNitro9.696s (+200.6% 🔺)11.640s (+141.4% 🔺)1.944s81.19x

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

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.558s (-31.8% 🟢)1.005s (-1.2%)0.447s1201.00x
🐘 PostgresNitro0.652s (-17.5% 🟢)1.006s (~)0.354s1201.17x
💻 LocalExpress9.153s (-18.2% 🟢)9.795s (-18.0% 🟢)0.643s1316.40x
💻 LocalNitro10.117s (-9.6% 🟢)10.778s (-7.6% 🟢)0.661s1218.13x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.676s (+119.6% 🔺)24.944s (+103.0% 🔺)2.268s51.00x
▲ VercelNitro23.736s (+207.4% 🔺)25.097s (+167.0% 🔺)1.361s51.05x
▲ VercelExpress28.260s (+280.9% 🔺)30.473s (+229.6% 🔺)2.213s41.25x

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

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.126s (+465.4% 🔺)2.004s (+99.5% 🔺)0.010s (-19.8% 🟢)2.016s (+98.0% 🔺)0.890s101.00x
🐘 PostgresExpress1.133s (+452.5% 🔺)2.002s (+100.5% 🔺)0.001s (-25.0% 🟢)2.010s (+98.8% 🔺)0.877s101.01x
💻 LocalNitro1.143s (+434.8% 🔺)2.005s (+99.6% 🔺)0.012s (-2.4%)2.019s (+98.2% 🔺)0.877s101.02x
🐘 PostgresNitro1.157s (+464.4% 🔺)1.999s (+100.0% 🔺)0.001s (-20.0% 🟢)2.010s (+98.8% 🔺)0.853s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)2.185s (-68.1% 🟢)3.165s (-63.4% 🟢)1.925s (+204.7% 🔺)5.496s (-43.9% 🟢)3.311s101.00x
▲ VercelNitro2.295s (-40.1% 🟢)3.603s (-31.7% 🟢)13.820s (+1762.3% 🔺)17.886s (+175.9% 🔺)15.591s101.05x
▲ VercelExpress2.329s (-7.0% 🟢)3.832s (-6.3% 🟢)1.518s (+58.0% 🔺)5.905s (+5.6% 🔺)3.576s101.07x

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

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.490s (+136.5% 🔺)2.005s (+99.2% 🔺)0.003s (-12.1% 🟢)2.022s (+97.7% 🔺)0.532s301.00x
🐘 PostgresNitro1.528s (+144.8% 🔺)2.003s (+98.9% 🔺)0.004s (-3.3%)2.027s (+98.2% 🔺)0.499s301.03x
💻 LocalNitro1.707s (+103.5% 🔺)2.011s (+98.7% 🔺)0.010s (+6.7% 🔺)2.202s (+97.3% 🔺)0.495s281.15x
💻 LocalExpress1.877s (+148.0% 🔺)2.011s (+95.5% 🔺)0.009s (-0.9%)2.394s (+130.2% 🔺)0.517s271.26x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.935s (-8.8% 🟢)7.705s (-3.8%)0.216s (-47.0% 🟢)8.447s (-4.4%)2.512s81.00x
▲ VercelNitro5.975s (-79.7% 🟢)7.548s (-75.5% 🟢)0.263s (+134.7% 🔺)8.277s (-74.0% 🟢)2.302s81.01x
▲ VercelNext.js (Turbopack)6.750s (-60.1% 🟢)7.706s (-57.8% 🟢)0.297s (+40.7% 🔺)8.648s (-54.3% 🟢)1.898s71.14x

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

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.637s (-34.3% 🟢)1.034s (-17.1% 🟢)0.000s (-58.6% 🟢)1.042s (-17.2% 🟢)0.405s581.00x
🐘 PostgresExpress0.647s (-32.7% 🟢)1.050s (-17.8% 🟢)0.000s (+58.6% 🔺)1.060s (-18.8% 🟢)0.413s581.02x
💻 LocalExpress1.339s (+9.3% 🔺)2.016s (~)0.001s (+70.0% 🔺)2.018s (~)0.679s302.10x
💻 LocalNitro1.726s (+41.2% 🔺)2.015s (~)0.000s (+140.0% 🔺)2.418s (+19.6% 🔺)0.691s252.71x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)3.534s (-65.3% 🟢)4.591s (-60.1% 🟢)0.000s (NaN%)5.094s (-57.7% 🟢)1.560s121.00x
▲ VercelNitro3.538s (+16.0% 🔺)4.997s (+13.8% 🔺)0.000s (+225.0% 🔺)5.453s (+13.4% 🔺)1.916s121.00x
▲ VercelExpress3.646s (-2.5%)5.083s (~)0.000s (~)5.696s (+3.0%)2.050s111.03x

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

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.294s (-27.0% 🟢)1.905s (-12.5% 🟢)0.000s (NaN%)1.924s (-12.5% 🟢)0.630s321.00x
🐘 PostgresNitro1.320s (-26.3% 🟢)2.031s (-5.1% 🟢)0.000s (-100.0% 🟢)2.043s (-6.0% 🟢)0.723s301.02x
💻 LocalExpress3.031s (-12.6% 🟢)3.777s (-6.4% 🟢)0.001s (-21.9% 🟢)3.780s (-6.3% 🟢)0.749s162.34x
💻 LocalNitro3.076s (-9.2% 🟢)3.777s (-6.3% 🟢)0.001s (-6.2% 🟢)3.779s (-6.4% 🟢)0.703s162.38x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.447s (+33.1% 🔺)7.047s (+31.1% 🔺)0.000s (-54.2% 🟢)7.517s (+29.7% 🔺)2.070s81.00x
▲ VercelNext.js (Turbopack)5.517s (-1.8%)7.043s (+0.9%)0.000s (~)7.537s (~)2.020s81.01x
▲ VercelExpress6.217s (+35.5% 🔺)7.655s (+27.1% 🔺)0.000s (NaN%)8.501s (+31.7% 🔺)2.284s81.14x

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

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress17/21
🐘 PostgresExpress18/21
▲ VercelNitro9/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)▲ Vercel21/21
Nitro🐘 Postgres15/21
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)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production120002191419
✅ 💻 Local Development158702191806
✅ 📦 Local Production158702191806
✅ 🐘 Local Postgres158702191806
✅ 🪟 Windows12900129
✅ 📋 Other7270176903
Total6817010527869

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro103026
✅ example103026
✅ express103026
✅ fastify103026
✅ hono103026
✅ nextjs-turbopack12702
✅ nextjs-webpack12702
✅ nitro103026
✅ nuxt103026
✅ sveltekit12207
✅ vite103026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack12900
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable104025
✅ e2e-local-dev-tanstack-start-104025
✅ e2e-local-postgres-nest-stable104025
✅ e2e-local-postgres-tanstack-start-104025
✅ e2e-local-prod-nest-stable104025
✅ e2e-local-prod-tanstack-start-104025
✅ e2e-vercel-prod-tanstack-start103026

📋 View full workflow run

Comment threadpackages/nitro/src/index.ts Outdated
@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably know better than me whether the nits apply, so use your judgement there. Happy to approve this once the e2e tests are running again and CI is green

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: blocking issues found

Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.test.ts
Comment threadpackages/builders/src/types.ts Outdated
* Extra esbuild options merged into every bundle this builder produces
* (steps, intermediate workflow, final workflow wrapper, webhook).
*/
esbuildOptions?: Partial<BuildOptions>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

esbuildOptions: Partial<BuildOptions> is a broad surface for one knob

The new field accepts the full esbuild BuildOptions and is spread into four bundle calls with very different needs (steps: node/esm, workflow VM: neutral/cjs, webhook: node/esm, final wrapper: node/esm). The current spread order puts user options first so builder-required options like platform, format, bundle, plugins win — that's defensive — but the API still implies "you can pass any esbuild option" when in practice almost everything that matters is overridden.

Given the only consumer is sourcesContent: false, consider either narrowing the public type to a small allowlist or naming the field something closer to its actual purpose. Not blocking, but the surface area will be hard to take back later.

@VaguelySerious

Copy link
Copy Markdown
Member

Seems like tests are still failing, please re-ping me when they pass 👋

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

Hey @VaguelySerious, the related tests are passing 🥳

@vercel

vercelBot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Deployment failed with the following error:

The Preview Deployment Suffix domain "labs.vercel.dev" is not available under the current scope. Learn more: https://vercel.link/3a1

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: no blocking issues

Comment threadpackages/nitro/src/index.ts Outdated
};

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The webhook handler is registered at /.well-known/workflow/v1/webhook/:token (line 214), but this functionRules key uses webhook/**. I built workbench/nitro-v3 with NITRO_PRESET=vercel and workflow.runtime: 'nodejs22.x' and verified that nitro's vercel preset generates two function dirs in this case:

  • webhook/[token].func — created via routeFuncRouter.match against the :token route, gets the runtime override.
  • webhook/[...].func — created from this ** rule, also gets the runtime override.

In config.json, the webhook/(?:.*) route emitted by this rule comes before webhook/(?<token>[^/]+), so [token].func ends up unreachable. The result is a wasted function dir (extra disk/build/deploy size) and slightly looser routing (/webhook/ with no token now matches).

Suggested fix: use the same pattern as the handler, e.g. '/.well-known/workflow/v1/webhook/:token'. With matching keys, nitro reuses the single funcDir under the runtime override.

// V2 combined: a single trigger covers both `__wkf_workflow_*`
// (workflow orchestration) and `__wkf_step_*` (step execution),
// since the same handler dispatches both.
experimentalTriggers: [WORKFLOW_QUEUE_TRIGGER],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The comment two lines above says "merged with any user-defined rules at the same paths so explicit user config (e.g. memory) is preserved", but the spread order here (user first, then maxDuration / experimentalTriggers) means workflow's values always win. Only fields that workflow doesn't touch (like memory) survive — user-set maxDuration, runtime, or additional experimentalTriggers get clobbered.

Two small things:

  1. The comment should match the behavior — something like "workflow's required triggers/maxDuration take precedence; unrelated fields like memory are preserved."
  2. Other integrations (e.g. packages/sveltekit) concatenate user triggers rather than replacing them. Probably not an issue in practice since this is a workflow-owned route, but worth deciding deliberately.

Also (related): also exercised by the E2E Vercel Prod Tests (hono) job, which currently fails on maxRetries=0 disables retries (60s timeout) on this PR while passing on main. And E2E Local Prod Tests (tanstack-start) fails on abortVoidSleepTimeoutWorkflow (also passes on main). Both look like potential regressions worth investigating before merge — they aren't in the known-flaky set.

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';
rules[webhookPath] = { ...rules[webhookPath], runtime };
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

runtime is propagated to the flow and webhook routes but not to the manifest route (/.well-known/workflow/v1/manifest.json, registered via addManifestHandler). If a user picks a non-default runtime, the manifest gets served from the catch-all __server function on the default runtime. Low impact (manifest is static JSON), but inconsistent — worth either documenting why it's intentionally excluded, or applying the same override.

return majorVersion === 2;
}
return !nitro.routing;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

This PR adds non-trivial new logic (isNitroV2, the useLegacyVercelBuild branch, the functionRules merge) with no unit test coverage.

Also heads-up: the existing packages/nitro/src/index.test.ts is already broken on main (2 of 10 tests reference #workflow/steps.mjs, which no longer exists since the V1→V2 combined bundle change). Not introduced by this PR, but since the file is already touching the same area, fixing those tests and adding a couple of cases for isNitroV2 and the v3-vercel branch would be a low-cost addition.

Comment threadpackages/nitro/src/builders.ts Outdated
*/
function getNitroStringExternals(nitro: Nitro): string[] | undefined {
const externals = nitro.options.externals?.external?.filter(
const external = (nitro.options as { externals?: { external?: unknown[] } })

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

The structural cast widens external to unknown[] then filters strings — works, but a named alias would read better:

typeNitroV2Options={externals?: {external?: unknown[]}};constexternal=(nitro.optionsasNitroV2Options).externals?.external;

Non-blocking.

Comment threadworkbench/vite/vite.config.ts
The functionRule was keyed by `webhook/**` while the actual handler is
registered at `webhook/:token`, so nitro v3's vercel preset created a
second `webhook/[...].func` next to the real one and shadowed it in the
generated routes. Use the same `:token` pattern as the handler so the
runtime override applies to the function that actually serves traffic.
Also propagate `workflow.runtime` to the public manifest route for
consistency, clarify the merge-precedence comment, and refresh the unit
tests to cover the v3 functionRules path and the `isNitroV2` detection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Wielander <peter.wielander@vercel.com>
@VaguelySerious

Copy link
Copy Markdown
Member

auto-merge is on, waiting for CI to pass

Comment threadpnpm-workspace.yaml
enhanced-resolve: 5.19.0
esbuild: ^0.27.3
nitro: 3.0.1-alpha.1
nitro: ^3.0.260429-beta

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should nitro on the workbench not be a stable release? 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro v2 is nitropack and Nitro v3 is nitro so I think it should be fine. Most the things target Nitro v3 although it's not got a "stable" release yet till Nuxt v5.

@github-actionsgithub-actionsBot mentioned this pull request May 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for c1242e8 (AI decision).

This commit refactors @workflow/nitro to use Nitro v3's functionRules for workflow routes, a code path that does not exist on stable (stable still uses the legacy VercelBuilder for all Vercel deploys regardless of Nitro version). It also bumps the nitro catalog dependency from 3.0.1-alpha.1 to ^3.0.260429-beta, which is a substantial upstream change tied to features only used on main. The webhook pattern fix called out in the changeset only applies to the v3 functionRules path that isn't present on stable.

To override, re-run the Backport to stable workflow manually via workflow_dispatch with this commit SHA.

Sign up for freeto 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.

3 participants

@RihanArfan@VaguelySerious@pi0
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

refactor(nitro): use nitro v3 functionRules workflow routes - #1575

Merged
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement
May 20, 2026
Merged

refactor(nitro): use nitro v3 functionRules workflow routes#1575
VaguelySerious merged 12 commits into
mainfrom
feat/nitro-builder-improvement

Conversation

@RihanArfan

@RihanArfanRihanArfan commented Apr 1, 2026

Copy link
Copy Markdown
Member

Description

Currently we separately build the Workflows functions for the Vercel Build Output API when deploying a Nitro app to Vercel. This means the Workflows routes aren't part of the Nitro bundle.

This PR changes to now handle the Workflows routes as handlers within Nitro v3. Nitro v2 is untouched.

How did you test your changes?

Locally ran nitro dev in the nitro-v3 workspace and visited the API route POST /api/test-direct-step-call.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR
    • During beta, we only use "patch" mode for changes. Don't tag minor/major versions.
    • Use pnpm changeset --empty if you are changing documentation or workbench apps
  • 🔒 DCO sign-off passes (run git commit --signoff on your commits)
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete

@changeset-bot

changeset-botBot commented Apr 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2c843be

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

This PR includes changesets to release 17 packages
NameType
@workflow/nitroPatch
@workflow/nuxtPatch
workflowPatch
@workflow/aiPatch
@workflow/world-testingPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Comment threadpackages/nitro/src/index.ts
@RihanArfan
RihanArfanforce-pushed the feat/nitro-builder-improvement branch from e312e97 to 3eb081aCompareApril 7, 2026 10:28
@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.030s (-29.7% 🟢)1.006s (~)0.975s101.00x
💻 LocalExpress0.031s (-29.6% 🟢)1.005s (~)0.974s101.03x
🐘 PostgresExpress0.043s (-25.5% 🟢)1.010s (~)0.967s101.43x
🐘 PostgresNitro0.048s (-49.2% 🟢)1.010s (-3.1%)0.962s101.60x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.224s (-10.9% 🟢)1.890s (-19.0% 🟢)1.666s101.00x
▲ VercelNitro0.225s (-45.0% 🟢)2.732s (+8.9% 🔺)2.507s101.01x
▲ VercelExpress0.397s (+68.6% 🔺)2.027s (-5.1% 🟢)1.630s101.77x

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

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.072s (-5.3% 🟢)2.007s (~)0.935s101.00x
💻 LocalExpress1.075s (-4.5%)2.006s (~)0.931s101.00x
🐘 PostgresExpress1.082s (-5.6% 🟢)2.009s (~)0.927s101.01x
🐘 PostgresNitro1.083s (-5.0%)2.010s (~)0.927s101.01x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.539s (-17.9% 🟢)3.295s (-13.5% 🟢)1.756s101.00x
▲ VercelNext.js (Turbopack)1.689s (-17.0% 🟢)4.668s (+21.9% 🔺)2.979s101.10x
▲ VercelNitro2.027s (-47.9% 🟢)3.895s (-34.1% 🟢)1.869s101.32x

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

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.412s (-4.7%)11.021s (~)0.608s31.00x
🐘 PostgresExpress10.416s (-5.0%)11.016s (~)0.600s31.00x
💻 LocalNitro10.424s (-4.8%)11.021s (~)0.598s31.00x
🐘 PostgresNitro10.433s (-4.0%)11.020s (~)0.586s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)13.515s (-22.0% 🟢)15.908s (-18.0% 🟢)2.392s21.00x
▲ VercelExpress13.568s (-20.1% 🟢)15.557s (-22.3% 🟢)1.989s21.00x
▲ VercelNitro13.587s (-42.7% 🟢)15.559s (-38.1% 🟢)1.972s21.01x

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

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.456s (-7.7% 🟢)14.017s (-6.7% 🟢)0.561s51.00x
💻 LocalExpress13.459s (-10.1% 🟢)14.025s (-6.7% 🟢)0.566s51.00x
🐘 PostgresNitro13.466s (-7.7% 🟢)14.019s (-6.7% 🟢)0.553s51.00x
💻 LocalNitro13.479s (-10.5% 🟢)14.027s (-12.5% 🟢)0.548s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express20.282s (-59.7% 🟢)22.320s (-57.5% 🟢)2.038s31.00x
▲ VercelNext.js (Turbopack)20.648s (-60.7% 🟢)22.415s (-59.0% 🟢)1.768s31.02x
▲ VercelNitro20.776s (-67.8% 🟢)22.345s (-66.5% 🟢)1.569s31.02x

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

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express11.896s (-28.3% 🟢)12.020s (-29.4% 🟢)0.124s81.00x
💻 LocalNitro11.919s (-29.0% 🟢)12.022s (-29.4% 🟢)0.103s81.00x
🐘 PostgresExpress11.979s (-14.5% 🟢)12.268s (-15.9% 🟢)0.288s81.01x
🐘 PostgresNitro12.158s (-12.9% 🟢)12.872s (-10.0% 🟢)0.714s71.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express30.115s (-75.2% 🟢)32.130s (-74.0% 🟢)2.014s31.00x
▲ VercelNitro30.529s (-92.8% 🟢)32.167s (-92.4% 🟢)1.638s31.01x
▲ VercelNext.js (Turbopack)31.252s (-92.1% 🟢)32.624s (-91.7% 🟢)1.372s31.04x

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

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.138s (-9.7% 🟢)2.007s (~)0.870s151.00x
💻 LocalExpress1.166s (-21.7% 🟢)2.006s (~)0.840s151.02x
💻 LocalNitro1.175s (-28.0% 🟢)2.006s (-3.3%)0.830s151.03x
🐘 PostgresNitro1.238s (-2.9%)2.074s (+3.2%)0.836s151.09x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.605s (-7.5% 🟢)3.989s (-7.7% 🟢)1.383s91.00x
▲ VercelExpress2.632s (-8.0% 🟢)4.521s (-2.2%)1.889s71.01x
▲ VercelNext.js (Turbopack)2.660s (-21.7% 🟢)4.213s (-14.6% 🟢)1.553s81.02x

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

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.199s (-49.2% 🟢)2.006s (-33.3% 🟢)0.807s151.00x
🐘 PostgresNitro1.234s (-47.5% 🟢)2.008s (-33.3% 🟢)0.773s151.03x
💻 LocalExpress1.611s (-45.5% 🟢)2.006s (-41.9% 🟢)0.395s151.34x
💻 LocalNitro1.781s (-43.3% 🟢)2.005s (-48.4% 🟢)0.224s151.49x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.495s (-3.4%)5.288s (+3.5%)1.793s61.00x
▲ VercelNext.js (Turbopack)3.647s (-48.6% 🟢)5.434s (-39.0% 🟢)1.787s61.04x
▲ VercelNitro4.003s (-1.2%)5.826s (-1.6%)1.823s61.15x

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

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.290s (-63.0% 🟢)2.007s (-50.0% 🟢)0.717s151.00x
🐘 PostgresNitro1.350s (-61.2% 🟢)2.008s (-49.9% 🟢)0.659s151.05x
💻 LocalExpress4.222s (-49.4% 🟢)4.582s (-49.2% 🟢)0.361s73.27x
💻 LocalNitro5.125s (-38.6% 🟢)5.678s (-37.1% 🟢)0.554s63.97x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.678s (+32.7% 🔺)6.756s (+22.1% 🔺)2.078s51.00x
▲ VercelExpress6.267s (+47.8% 🔺)8.267s (+34.9% 🔺)2.000s41.34x
▲ VercelNext.js (Turbopack)7.564s (-15.2% 🟢)9.835s (-10.3% 🟢)2.271s41.62x

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

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.145s (-8.9% 🟢)2.008s (~)0.863s151.00x
🐘 PostgresNitro1.149s (-8.6% 🟢)2.008s (~)0.859s151.00x
💻 LocalExpress1.361s (-28.1% 🟢)2.006s (-15.1% 🟢)0.645s151.19x
💻 LocalNitro1.371s (-26.5% 🟢)2.006s (-14.3% 🟢)0.635s151.20x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.498s (+1.6%)4.099s (-1.7%)1.601s81.00x
▲ VercelNext.js (Turbopack)2.715s (-7.4% 🟢)4.421s (-4.8%)1.707s71.09x
▲ VercelExpress2.897s (+12.2% 🔺)4.519s (+3.9%)1.622s71.16x

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

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.192s (-49.1% 🟢)2.008s (-33.3% 🟢)0.817s151.00x
🐘 PostgresExpress1.198s (-48.8% 🟢)2.009s (-33.3% 🟢)0.811s151.01x
💻 LocalExpress1.702s (-45.7% 🟢)2.008s (-46.6% 🟢)0.306s151.43x
💻 LocalNitro2.025s (-33.9% 🟢)2.391s (-38.5% 🟢)0.366s131.70x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.357s (+3.8%)5.248s (+3.4%)1.892s61.00x
▲ VercelExpress3.634s (+13.8% 🔺)5.286s (+10.3% 🔺)1.652s61.08x
▲ VercelNext.js (Turbopack)4.356s (+38.6% 🔺)6.224s (+37.6% 🔺)1.868s61.30x

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

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.271s (-63.7% 🟢)2.007s (-50.0% 🟢)0.735s151.00x
🐘 PostgresNitro1.304s (-62.5% 🟢)2.007s (-49.9% 🟢)0.703s151.03x
💻 LocalExpress4.961s (-43.6% 🟢)5.514s (-40.5% 🟢)0.553s63.90x
💻 LocalNitro6.189s (-32.3% 🟢)6.415s (-36.0% 🟢)0.226s54.87x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.914s (-3.5%)6.839s (~)1.925s51.00x
▲ VercelExpress5.278s (-17.7% 🟢)7.562s (-7.5% 🟢)2.284s41.07x
▲ VercelNext.js (Turbopack)6.788s (~)8.497s (-0.5%)1.709s41.38x

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

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.434s (-48.2% 🟢)1.006s (-1.6%)0.572s601.00x
🐘 PostgresNitro0.454s (-44.7% 🟢)1.024s (+1.8%)0.570s591.05x
💻 LocalNitro0.485s (-50.5% 🟢)1.004s (-8.2% 🟢)0.519s601.12x
💻 LocalExpress0.563s (-42.7% 🟢)1.092s (+1.5%)0.528s571.30x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.763s (-78.4% 🟢)6.470s (-73.1% 🟢)1.707s101.00x
▲ VercelNext.js (Turbopack)5.279s (-63.6% 🟢)6.979s (-56.6% 🟢)1.700s91.11x
▲ VercelExpress5.386s (-71.7% 🟢)7.590s (-64.4% 🟢)2.204s81.13x

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

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.038s (-46.1% 🟢)1.791s (-14.7% 🟢)0.753s511.00x
🐘 PostgresExpress1.094s (-44.7% 🟢)1.705s (-24.5% 🟢)0.611s531.05x
💻 LocalExpress1.163s (-61.4% 🟢)2.005s (-44.1% 🟢)0.842s451.12x
💻 LocalNitro1.200s (-60.5% 🟢)2.006s (-46.6% 🟢)0.806s451.16x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.272s (-68.9% 🟢)14.115s (-65.8% 🟢)1.842s71.00x
▲ VercelNext.js (Turbopack)13.606s (-72.7% 🟢)15.509s (-70.0% 🟢)1.903s61.11x
▲ VercelExpress13.810s (-60.0% 🟢)16.683s (-54.7% 🟢)2.872s61.13x

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

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.047s (-48.7% 🟢)2.506s (-42.6% 🟢)0.459s481.00x
🐘 PostgresNitro2.132s (-48.0% 🟢)2.798s (-39.2% 🟢)0.666s431.04x
💻 LocalExpress2.603s (-71.7% 🟢)3.007s (-70.0% 🟢)0.404s401.27x
💻 LocalNitro2.699s (-71.0% 🟢)3.008s (-70.0% 🟢)0.308s401.32x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro39.315s (-59.4% 🟢)41.156s (-58.2% 🟢)1.841s31.00x
▲ VercelExpress40.781s (-68.6% 🟢)43.325s (-67.2% 🟢)2.544s31.04x
▲ VercelNext.js (Turbopack)41.245s (-61.5% 🟢)42.926s (-60.6% 🟢)1.681s31.05x

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

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.165s (-41.6% 🟢)1.006s (~)0.841s601.00x
🐘 PostgresNitro0.172s (-39.4% 🟢)1.006s (~)0.834s601.04x
💻 LocalExpress0.421s (-24.9% 🟢)1.004s (~)0.583s602.55x
💻 LocalNitro0.463s (-23.5% 🟢)1.004s (-1.7%)0.541s602.81x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.161s (+10.6% 🔺)4.409s (+21.2% 🔺)2.249s141.00x
▲ VercelNext.js (Turbopack)2.599s (+28.5% 🔺)4.308s (+13.6% 🔺)1.709s141.20x
▲ VercelNitro2.656s (+59.9% 🔺)4.513s (+34.7% 🔺)1.858s141.23x

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

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.285s (-44.0% 🟢)1.006s (~)0.720s901.00x
🐘 PostgresNitro0.298s (-40.0% 🟢)1.006s (~)0.708s901.04x
💻 LocalExpress2.156s (-14.2% 🟢)2.685s (-10.8% 🟢)0.529s347.56x
💻 LocalNitro2.179s (-14.2% 🟢)2.912s (-3.2%)0.733s317.64x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)8.136s (+130.1% 🔺)10.219s (+96.8% 🔺)2.083s91.00x
▲ VercelExpress8.249s (+170.8% 🔺)10.683s (+122.2% 🔺)2.434s91.01x
▲ VercelNitro9.696s (+200.6% 🔺)11.640s (+141.4% 🔺)1.944s81.19x

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

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.558s (-31.8% 🟢)1.005s (-1.2%)0.447s1201.00x
🐘 PostgresNitro0.652s (-17.5% 🟢)1.006s (~)0.354s1201.17x
💻 LocalExpress9.153s (-18.2% 🟢)9.795s (-18.0% 🟢)0.643s1316.40x
💻 LocalNitro10.117s (-9.6% 🟢)10.778s (-7.6% 🟢)0.661s1218.13x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.676s (+119.6% 🔺)24.944s (+103.0% 🔺)2.268s51.00x
▲ VercelNitro23.736s (+207.4% 🔺)25.097s (+167.0% 🔺)1.361s51.05x
▲ VercelExpress28.260s (+280.9% 🔺)30.473s (+229.6% 🔺)2.213s41.25x

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

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.126s (+465.4% 🔺)2.004s (+99.5% 🔺)0.010s (-19.8% 🟢)2.016s (+98.0% 🔺)0.890s101.00x
🐘 PostgresExpress1.133s (+452.5% 🔺)2.002s (+100.5% 🔺)0.001s (-25.0% 🟢)2.010s (+98.8% 🔺)0.877s101.01x
💻 LocalNitro1.143s (+434.8% 🔺)2.005s (+99.6% 🔺)0.012s (-2.4%)2.019s (+98.2% 🔺)0.877s101.02x
🐘 PostgresNitro1.157s (+464.4% 🔺)1.999s (+100.0% 🔺)0.001s (-20.0% 🟢)2.010s (+98.8% 🔺)0.853s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)2.185s (-68.1% 🟢)3.165s (-63.4% 🟢)1.925s (+204.7% 🔺)5.496s (-43.9% 🟢)3.311s101.00x
▲ VercelNitro2.295s (-40.1% 🟢)3.603s (-31.7% 🟢)13.820s (+1762.3% 🔺)17.886s (+175.9% 🔺)15.591s101.05x
▲ VercelExpress2.329s (-7.0% 🟢)3.832s (-6.3% 🟢)1.518s (+58.0% 🔺)5.905s (+5.6% 🔺)3.576s101.07x

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

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.490s (+136.5% 🔺)2.005s (+99.2% 🔺)0.003s (-12.1% 🟢)2.022s (+97.7% 🔺)0.532s301.00x
🐘 PostgresNitro1.528s (+144.8% 🔺)2.003s (+98.9% 🔺)0.004s (-3.3%)2.027s (+98.2% 🔺)0.499s301.03x
💻 LocalNitro1.707s (+103.5% 🔺)2.011s (+98.7% 🔺)0.010s (+6.7% 🔺)2.202s (+97.3% 🔺)0.495s281.15x
💻 LocalExpress1.877s (+148.0% 🔺)2.011s (+95.5% 🔺)0.009s (-0.9%)2.394s (+130.2% 🔺)0.517s271.26x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.935s (-8.8% 🟢)7.705s (-3.8%)0.216s (-47.0% 🟢)8.447s (-4.4%)2.512s81.00x
▲ VercelNitro5.975s (-79.7% 🟢)7.548s (-75.5% 🟢)0.263s (+134.7% 🔺)8.277s (-74.0% 🟢)2.302s81.01x
▲ VercelNext.js (Turbopack)6.750s (-60.1% 🟢)7.706s (-57.8% 🟢)0.297s (+40.7% 🔺)8.648s (-54.3% 🟢)1.898s71.14x

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

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.637s (-34.3% 🟢)1.034s (-17.1% 🟢)0.000s (-58.6% 🟢)1.042s (-17.2% 🟢)0.405s581.00x
🐘 PostgresExpress0.647s (-32.7% 🟢)1.050s (-17.8% 🟢)0.000s (+58.6% 🔺)1.060s (-18.8% 🟢)0.413s581.02x
💻 LocalExpress1.339s (+9.3% 🔺)2.016s (~)0.001s (+70.0% 🔺)2.018s (~)0.679s302.10x
💻 LocalNitro1.726s (+41.2% 🔺)2.015s (~)0.000s (+140.0% 🔺)2.418s (+19.6% 🔺)0.691s252.71x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)3.534s (-65.3% 🟢)4.591s (-60.1% 🟢)0.000s (NaN%)5.094s (-57.7% 🟢)1.560s121.00x
▲ VercelNitro3.538s (+16.0% 🔺)4.997s (+13.8% 🔺)0.000s (+225.0% 🔺)5.453s (+13.4% 🔺)1.916s121.00x
▲ VercelExpress3.646s (-2.5%)5.083s (~)0.000s (~)5.696s (+3.0%)2.050s111.03x

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

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.294s (-27.0% 🟢)1.905s (-12.5% 🟢)0.000s (NaN%)1.924s (-12.5% 🟢)0.630s321.00x
🐘 PostgresNitro1.320s (-26.3% 🟢)2.031s (-5.1% 🟢)0.000s (-100.0% 🟢)2.043s (-6.0% 🟢)0.723s301.02x
💻 LocalExpress3.031s (-12.6% 🟢)3.777s (-6.4% 🟢)0.001s (-21.9% 🟢)3.780s (-6.3% 🟢)0.749s162.34x
💻 LocalNitro3.076s (-9.2% 🟢)3.777s (-6.3% 🟢)0.001s (-6.2% 🟢)3.779s (-6.4% 🟢)0.703s162.38x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.447s (+33.1% 🔺)7.047s (+31.1% 🔺)0.000s (-54.2% 🟢)7.517s (+29.7% 🔺)2.070s81.00x
▲ VercelNext.js (Turbopack)5.517s (-1.8%)7.043s (+0.9%)0.000s (~)7.537s (~)2.020s81.01x
▲ VercelExpress6.217s (+35.5% 🔺)7.655s (+27.1% 🔺)0.000s (NaN%)8.501s (+31.7% 🔺)2.284s81.14x

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

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress17/21
🐘 PostgresExpress18/21
▲ VercelNitro9/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)▲ Vercel21/21
Nitro🐘 Postgres15/21
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)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production120002191419
✅ 💻 Local Development158702191806
✅ 📦 Local Production158702191806
✅ 🐘 Local Postgres158702191806
✅ 🪟 Windows12900129
✅ 📋 Other7270176903
Total6817010527869

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro103026
✅ example103026
✅ express103026
✅ fastify103026
✅ hono103026
✅ nextjs-turbopack12702
✅ nextjs-webpack12702
✅ nitro103026
✅ nuxt103026
✅ sveltekit12207
✅ vite103026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable104025
✅ express-stable104025
✅ fastify-stable104025
✅ hono-stable104025
✅ nextjs-turbopack-canary110019
✅ nextjs-turbopack-stable-lazy-discovery-disabled12900
✅ nextjs-turbopack-stable-lazy-discovery-enabled12900
✅ nextjs-webpack-canary110019
✅ nextjs-webpack-stable-lazy-discovery-disabled12900
✅ nextjs-webpack-stable-lazy-discovery-enabled12900
✅ nitro-stable104025
✅ nuxt-stable104025
✅ sveltekit-stable12306
✅ vite-stable104025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack12900
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable104025
✅ e2e-local-dev-tanstack-start-104025
✅ e2e-local-postgres-nest-stable104025
✅ e2e-local-postgres-tanstack-start-104025
✅ e2e-local-prod-nest-stable104025
✅ e2e-local-prod-tanstack-start-104025
✅ e2e-vercel-prod-tanstack-start103026

📋 View full workflow run

Comment threadpackages/nitro/src/index.ts Outdated
@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably know better than me whether the nits apply, so use your judgement there. Happy to approve this once the e2e tests are running again and CI is green

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: blocking issues found

Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.ts Outdated
Comment threadpackages/nitro/src/index.test.ts
Comment threadpackages/builders/src/types.ts Outdated
* Extra esbuild options merged into every bundle this builder produces
* (steps, intermediate workflow, final workflow wrapper, webhook).
*/
esbuildOptions?: Partial<BuildOptions>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

esbuildOptions: Partial<BuildOptions> is a broad surface for one knob

The new field accepts the full esbuild BuildOptions and is spread into four bundle calls with very different needs (steps: node/esm, workflow VM: neutral/cjs, webhook: node/esm, final wrapper: node/esm). The current spread order puts user options first so builder-required options like platform, format, bundle, plugins win — that's defensive — but the API still implies "you can pass any esbuild option" when in practice almost everything that matters is overridden.

Given the only consumer is sourcesContent: false, consider either narrowing the public type to a small allowlist or naming the field something closer to its actual purpose. Not blocking, but the surface area will be hard to take back later.

@VaguelySerious

Copy link
Copy Markdown
Member

Seems like tests are still failing, please re-ping me when they pass 👋

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

Hey @VaguelySerious, the related tests are passing 🥳

@vercel

vercelBot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Deployment failed with the following error:

The Preview Deployment Suffix domain "labs.vercel.dev" is not available under the current scope. Learn more: https://vercel.link/3a1

@RihanArfan

Copy link
Copy Markdown
MemberAuthor

@vercel/workflow

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: no blocking issues

Comment threadpackages/nitro/src/index.ts Outdated
};

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The webhook handler is registered at /.well-known/workflow/v1/webhook/:token (line 214), but this functionRules key uses webhook/**. I built workbench/nitro-v3 with NITRO_PRESET=vercel and workflow.runtime: 'nodejs22.x' and verified that nitro's vercel preset generates two function dirs in this case:

  • webhook/[token].func — created via routeFuncRouter.match against the :token route, gets the runtime override.
  • webhook/[...].func — created from this ** rule, also gets the runtime override.

In config.json, the webhook/(?:.*) route emitted by this rule comes before webhook/(?<token>[^/]+), so [token].func ends up unreachable. The result is a wasted function dir (extra disk/build/deploy size) and slightly looser routing (/webhook/ with no token now matches).

Suggested fix: use the same pattern as the handler, e.g. '/.well-known/workflow/v1/webhook/:token'. With matching keys, nitro reuses the single funcDir under the runtime override.

// V2 combined: a single trigger covers both `__wkf_workflow_*`
// (workflow orchestration) and `__wkf_step_*` (step execution),
// since the same handler dispatches both.
experimentalTriggers: [WORKFLOW_QUEUE_TRIGGER],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

The comment two lines above says "merged with any user-defined rules at the same paths so explicit user config (e.g. memory) is preserved", but the spread order here (user first, then maxDuration / experimentalTriggers) means workflow's values always win. Only fields that workflow doesn't touch (like memory) survive — user-set maxDuration, runtime, or additional experimentalTriggers get clobbered.

Two small things:

  1. The comment should match the behavior — something like "workflow's required triggers/maxDuration take precedence; unrelated fields like memory are preserved."
  2. Other integrations (e.g. packages/sveltekit) concatenate user triggers rather than replacing them. Probably not an issue in practice since this is a workflow-owned route, but worth deciding deliberately.

Also (related): also exercised by the E2E Vercel Prod Tests (hono) job, which currently fails on maxRetries=0 disables retries (60s timeout) on this PR while passing on main. And E2E Local Prod Tests (tanstack-start) fails on abortVoidSleepTimeoutWorkflow (also passes on main). Both look like potential regressions worth investigating before merge — they aren't in the known-flaky set.

if (runtime) {
const webhookPath = '/.well-known/workflow/v1/webhook/**';
rules[webhookPath] = { ...rules[webhookPath], runtime };
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

runtime is propagated to the flow and webhook routes but not to the manifest route (/.well-known/workflow/v1/manifest.json, registered via addManifestHandler). If a user picks a non-default runtime, the manifest gets served from the catch-all __server function on the default runtime. Low impact (manifest is static JSON), but inconsistent — worth either documenting why it's intentionally excluded, or applying the same override.

return majorVersion === 2;
}
return !nitro.routing;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Note

This PR adds non-trivial new logic (isNitroV2, the useLegacyVercelBuild branch, the functionRules merge) with no unit test coverage.

Also heads-up: the existing packages/nitro/src/index.test.ts is already broken on main (2 of 10 tests reference #workflow/steps.mjs, which no longer exists since the V1→V2 combined bundle change). Not introduced by this PR, but since the file is already touching the same area, fixing those tests and adding a couple of cases for isNitroV2 and the v3-vercel branch would be a low-cost addition.

Comment threadpackages/nitro/src/builders.ts Outdated
*/
function getNitroStringExternals(nitro: Nitro): string[] | undefined {
const externals = nitro.options.externals?.external?.filter(
const external = (nitro.options as { externals?: { external?: unknown[] } })

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Nit

The structural cast widens external to unknown[] then filters strings — works, but a named alias would read better:

typeNitroV2Options={externals?: {external?: unknown[]}};constexternal=(nitro.optionsasNitroV2Options).externals?.external;

Non-blocking.

Comment threadworkbench/vite/vite.config.ts
The functionRule was keyed by `webhook/**` while the actual handler is
registered at `webhook/:token`, so nitro v3's vercel preset created a
second `webhook/[...].func` next to the real one and shadowed it in the
generated routes. Use the same `:token` pattern as the handler so the
runtime override applies to the function that actually serves traffic.
Also propagate `workflow.runtime` to the public manifest route for
consistency, clarify the merge-precedence comment, and refresh the unit
tests to cover the v3 functionRules path and the `isNitroV2` detection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Wielander <peter.wielander@vercel.com>
@VaguelySerious

Copy link
Copy Markdown
Member

auto-merge is on, waiting for CI to pass

Comment threadpnpm-workspace.yaml
enhanced-resolve: 5.19.0
esbuild: ^0.27.3
nitro: 3.0.1-alpha.1
nitro: ^3.0.260429-beta

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should nitro on the workbench not be a stable release? 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro v2 is nitropack and Nitro v3 is nitro so I think it should be fine. Most the things target Nitro v3 although it's not got a "stable" release yet till Nuxt v5.

@github-actionsgithub-actionsBot mentioned this pull request May 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for c1242e8 (AI decision).

This commit refactors @workflow/nitro to use Nitro v3's functionRules for workflow routes, a code path that does not exist on stable (stable still uses the legacy VercelBuilder for all Vercel deploys regardless of Nitro version). It also bumps the nitro catalog dependency from 3.0.1-alpha.1 to ^3.0.260429-beta, which is a substantial upstream change tied to features only used on main. The webhook pattern fix called out in the changeset only applies to the v3 functionRules path that isn't present on stable.

To override, re-run the Backport to stable workflow manually via workflow_dispatch with this commit SHA.

Sign up for freeto 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.

3 participants

@RihanArfan@VaguelySerious@pi0