Skip to content

Auto-remove workflow packages from serverExternalPackages#1481

Merged
TooTallNate merged 4 commits intomainfrom
nrajlich/warn-external-workflow-packages
May 5, 2026
Merged

Auto-remove workflow packages from serverExternalPackages#1481
TooTallNate merged 4 commits intomainfrom
nrajlich/warn-external-workflow-packages

Conversation

@TooTallNate
Copy link
Copy Markdown
Member

@TooTallNate TooTallNate commented Mar 22, 2026

Summary

  • Detect workflow-enabled packages in nextConfig.serverExternalPackages (\"use step\", \"use workflow\", or serialization patterns) and auto-remove them for the current build so Next.js can transform them.
  • Keep a best-effort warning fallback in builders for non-Next externalPackages, and update warning text to use externalPackages with a Next.js mapping note.
  • Update the Next API docs and changeset to reflect the new behavior.

Why

Warning-only behavior still leaves users on a failure path at runtime. Auto-removing detected workflow packages from serverExternalPackages provides a safer default UX while still giving users a clear warning and manual cleanup path in next.config.

Implementation details

  • withWorkflow() now scans configured serverExternalPackages, filters out detected workflow packages, and passes the filtered list to both Next config and builder externals.
  • The warning is emitted once per package and asks users to remove the package from serverExternalPackages to silence future warnings.
  • Builder fallback warning remains best-effort and now:
    • works in ESM via createRequire(import.meta.url)
    • avoids false positives for unresolved/virtual specifiers
    • dedupes on actual warning emission

Testing

  • pnpm vitest run packages/next/src/index.test.ts packages/builders/src/external-package-warning.test.ts
  • pnpm build (in packages/next)
  • pnpm build (in packages/builders)

Copilot AI review requested due to automatic review settings March 22, 2026 20:53
@TooTallNate TooTallNate requested a review from a team as a code owner March 22, 2026 20:53
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 22, 2026

🦋 Changeset detected

Latest commit: aa85a83

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

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

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Mar 22, 2026

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

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment May 5, 2026 9:00am
example-nextjs-workflow-webpack Ready Ready Preview, Comment May 5, 2026 9:00am
example-workflow Ready Ready Preview, Comment May 5, 2026 9:00am
workbench-astro-workflow Ready Ready Preview, Comment May 5, 2026 9:00am
workbench-express-workflow Ready Ready Preview, Comment May 5, 2026 9:00am
workbench-fastify-workflow Ready Ready Preview, Comment May 5, 2026 9:00am
workbench-hono-workflow Ready Ready Preview, Comment May 5, 2026 9:00am
workbench-nestjs-workflow Error Error May 5, 2026 9:00am
workbench-nitro-workflow Ready Ready Preview, Comment May 5, 2026 9:00am
workbench-nuxt-workflow Ready Ready Preview, Comment May 5, 2026 9:00am
workbench-sveltekit-workflow Ready Ready Preview, Comment May 5, 2026 9:00am
workbench-tanstack-start-workflow Ready Ready Preview, Comment May 5, 2026 9:00am
workbench-vite-workflow Ready Ready Preview, Comment May 5, 2026 9:00am
workflow-docs Ready Ready Preview, Comment, Open in v0 May 5, 2026 9:00am
workflow-swc-playground Ready Ready Preview, Comment May 5, 2026 9:00am
workflow-tarballs Ready Ready Preview, Comment May 5, 2026 9:00am
workflow-web Ready Ready Preview, Comment May 5, 2026 9:00am

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 22, 2026

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 0.029s (-31.8% 🟢) 1.005s (~) 0.976s 10 1.00x
💻 Local Express 0.031s (-29.3% 🟢) 1.006s (~) 0.974s 10 1.06x
🐘 Postgres Express 0.044s (-23.6% 🟢) 1.013s (~) 0.969s 10 1.51x
💻 Local Next.js (Turbopack) 0.048s 1.005s 0.957s 10 1.63x
🐘 Postgres Nitro 0.049s (-48.5% 🟢) 1.012s (-3.0%) 0.963s 10 1.67x
🌐 Redis Next.js (Turbopack) 0.052s 1.005s 0.953s 10 1.76x
🐘 Postgres Next.js (Turbopack) 0.057s 1.013s 0.956s 10 1.94x
🌐 MongoDB Next.js (Turbopack) 0.101s 1.007s 0.907s 10 3.42x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 0.197s (-51.8% 🟢) 1.800s (-28.3% 🟢) 1.603s 10 1.00x
▲ Vercel Next.js (Turbopack) 0.712s (+183.0% 🔺) 2.629s (+12.7% 🔺) 1.917s 10 3.61x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.067s (-5.7% 🟢) 2.006s (~) 0.939s 10 1.00x
💻 Local Express 1.076s (-4.4%) 2.007s (~) 0.931s 10 1.01x
🐘 Postgres Nitro 1.083s (-5.0% 🟢) 2.009s (~) 0.926s 10 1.01x
🐘 Postgres Express 1.091s (-4.8%) 2.010s (~) 0.919s 10 1.02x
💻 Local Next.js (Turbopack) 1.107s 2.006s 0.899s 10 1.04x
🌐 Redis Next.js (Turbopack) 1.114s 2.007s 0.893s 10 1.04x
🐘 Postgres Next.js (Turbopack) 1.122s 2.011s 0.889s 10 1.05x
🌐 MongoDB Next.js (Turbopack) 1.154s 2.008s 0.854s 10 1.08x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.526s (-60.8% 🟢) 3.736s (-36.8% 🟢) 2.210s 10 1.00x
▲ Vercel Next.js (Turbopack) 2.614s (+28.5% 🔺) 4.219s (+10.1% 🔺) 1.605s 10 1.71x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 10.398s (-5.0% 🟢) 11.022s (~) 0.624s 3 1.00x
💻 Local Express 10.424s (-4.6%) 11.023s (~) 0.599s 3 1.00x
🐘 Postgres Nitro 10.434s (-4.0%) 11.018s (~) 0.585s 3 1.00x
🐘 Postgres Express 10.458s (-4.6%) 11.020s (~) 0.562s 3 1.01x
🌐 Redis Next.js (Turbopack) 10.659s 11.023s 0.364s 3 1.03x
💻 Local Next.js (Turbopack) 10.679s 11.022s 0.343s 3 1.03x
🌐 MongoDB Next.js (Turbopack) 10.737s 11.020s 0.283s 3 1.03x
🐘 Postgres Next.js (Turbopack) 10.758s 11.016s 0.258s 3 1.03x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 13.264s (-44.1% 🟢) 15.766s (-37.2% 🟢) 2.502s 2 1.00x
▲ Vercel Next.js (Turbopack) 14.964s (-13.6% 🟢) 17.916s (-7.6% 🟢) 2.952s 2 1.13x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 13.468s (-10.6% 🟢) 14.026s (-12.5% 🟢) 0.558s 5 1.00x
🐘 Postgres Express 13.469s (-7.6% 🟢) 14.023s (-6.6% 🟢) 0.555s 5 1.00x
💻 Local Express 13.472s (-10.0% 🟢) 14.028s (-6.7% 🟢) 0.556s 5 1.00x
🐘 Postgres Nitro 13.494s (-7.5% 🟢) 14.017s (-6.7% 🟢) 0.523s 5 1.00x
🌐 Redis Next.js (Turbopack) 14.038s 14.629s 0.590s 5 1.04x
💻 Local Next.js (Turbopack) 14.104s 15.029s 0.924s 4 1.05x
🐘 Postgres Next.js (Turbopack) 14.170s 15.018s 0.848s 4 1.05x
🌐 MongoDB Next.js (Turbopack) 14.225s 15.022s 0.797s 4 1.06x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 20.674s (-67.9% 🟢) 22.736s (-65.9% 🟢) 2.062s 3 1.00x
▲ Vercel Next.js (Turbopack) 22.983s (-56.3% 🟢) 24.510s (-55.1% 🟢) 1.527s 3 1.11x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 11.845s (-29.4% 🟢) 12.022s (-29.4% 🟢) 0.177s 8 1.00x
🐘 Postgres Nitro 11.910s (-14.7% 🟢) 12.016s (-16.0% 🟢) 0.106s 8 1.01x
🐘 Postgres Express 11.921s (-14.9% 🟢) 12.017s (-17.7% 🟢) 0.096s 8 1.01x
💻 Local Express 11.958s (-28.0% 🟢) 12.273s (-27.9% 🟢) 0.315s 8 1.01x
🌐 Redis Next.js (Turbopack) 12.913s 13.025s 0.113s 7 1.09x
💻 Local Next.js (Turbopack) 13.008s 13.310s 0.302s 7 1.10x
🌐 MongoDB Next.js (Turbopack) 13.209s 14.021s 0.812s 7 1.12x
🐘 Postgres Next.js (Turbopack) 13.235s 14.015s 0.780s 7 1.12x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 30.665s (-92.7% 🟢) 32.863s (-92.3% 🟢) 2.197s 3 1.00x
▲ Vercel Next.js (Turbopack) 37.304s (-90.5% 🟢) 39.435s (-90.0% 🟢) 2.131s 3 1.22x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.153s (-8.5% 🟢) 2.007s (~) 0.855s 15 1.00x
🐘 Postgres Nitro 1.157s (-9.2% 🟢) 2.008s (~) 0.851s 15 1.00x
💻 Local Nitro 1.172s (-28.2% 🟢) 2.006s (-3.3%) 0.834s 15 1.02x
💻 Local Express 1.181s (-20.7% 🟢) 2.006s (~) 0.825s 15 1.02x
🐘 Postgres Next.js (Turbopack) 1.229s 2.007s 0.778s 15 1.07x
🌐 Redis Next.js (Turbopack) 1.234s 2.006s 0.773s 15 1.07x
💻 Local Next.js (Turbopack) 1.282s 2.005s 0.724s 15 1.11x
🌐 MongoDB Next.js (Turbopack) 2.026s 2.735s 0.709s 11 1.76x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.201s (+13.6% 🔺) 5.396s (+24.8% 🔺) 2.195s 6 1.00x
▲ Vercel Next.js (Turbopack) 5.146s (+51.4% 🔺) 6.750s (+36.8% 🔺) 1.604s 5 1.61x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.228s (-48.0% 🟢) 2.008s (-33.3% 🟢) 0.780s 15 1.00x
🐘 Postgres Nitro 1.235s (-47.5% 🟢) 2.007s (-33.3% 🟢) 0.771s 15 1.01x
🐘 Postgres Next.js (Turbopack) 1.384s 2.007s 0.624s 15 1.13x
💻 Local Nitro 1.702s (-45.9% 🟢) 2.005s (-48.4% 🟢) 0.304s 15 1.39x
💻 Local Next.js (Turbopack) 1.781s 2.073s 0.292s 15 1.45x
💻 Local Express 1.782s (-39.7% 🟢) 2.006s (-41.9% 🟢) 0.224s 15 1.45x
🌐 Redis Next.js (Turbopack) 2.362s 3.008s 0.646s 10 1.92x
🌐 MongoDB Next.js (Turbopack) 3.560s 4.008s 0.448s 8 2.90x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.416s (-15.7% 🟢) 5.708s (-3.6%) 2.292s 6 1.00x
▲ Vercel Next.js (Turbopack) 4.958s (-30.2% 🟢) 6.737s (-24.3% 🟢) 1.779s 5 1.45x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.404s (-59.7% 🟢) 2.007s (-49.9% 🟢) 0.603s 15 1.00x
🐘 Postgres Express 1.405s (-59.7% 🟢) 2.007s (-49.9% 🟢) 0.602s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.687s 2.008s 0.321s 15 1.20x
🌐 Redis Next.js (Turbopack) 3.568s 4.010s 0.442s 8 2.54x
💻 Local Nitro 4.616s (-44.7% 🟢) 5.179s (-42.6% 🟢) 0.563s 6 3.29x
💻 Local Express 4.902s (-41.2% 🟢) 5.345s (-40.8% 🟢) 0.443s 6 3.49x
💻 Local Next.js (Turbopack) 5.757s 6.213s 0.456s 5 4.10x
🌐 MongoDB Next.js (Turbopack) 6.277s 7.015s 0.738s 5 4.47x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 6.017s (-32.5% 🟢) 8.074s (-26.3% 🟢) 2.056s 4 1.00x
▲ Vercel Nitro 6.464s (+83.4% 🔺) 8.483s (+53.3% 🔺) 2.019s 5 1.07x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.166s (-7.3% 🟢) 2.010s (~) 0.845s 15 1.00x
🐘 Postgres Nitro 1.175s (-6.6% 🟢) 2.008s (~) 0.833s 15 1.01x
🐘 Postgres Next.js (Turbopack) 1.212s 2.007s 0.795s 15 1.04x
🌐 Redis Next.js (Turbopack) 1.224s 2.006s 0.783s 15 1.05x
💻 Local Next.js (Turbopack) 1.320s 2.006s 0.686s 15 1.13x
💻 Local Nitro 1.364s (-26.9% 🟢) 2.006s (-14.3% 🟢) 0.642s 15 1.17x
💻 Local Express 1.420s (-25.0% 🟢) 2.007s (-15.1% 🟢) 0.587s 15 1.22x
🌐 MongoDB Next.js (Turbopack) 2.041s 2.916s 0.875s 11 1.75x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.702s (+9.9% 🔺) 4.549s (+9.1% 🔺) 1.847s 7 1.00x
▲ Vercel Next.js (Turbopack) 4.801s (+63.8% 🔺) 6.614s (+42.5% 🔺) 1.813s 5 1.78x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.239s (-47.0% 🟢) 2.009s (-33.3% 🟢) 0.769s 15 1.00x
🐘 Postgres Express 1.270s (-45.8% 🟢) 2.009s (-33.3% 🟢) 0.739s 15 1.02x
🐘 Postgres Next.js (Turbopack) 1.374s 2.007s 0.633s 15 1.11x
💻 Local Nitro 1.809s (-41.0% 🟢) 2.076s (-46.6% 🟢) 0.267s 15 1.46x
💻 Local Express 1.952s (-37.7% 🟢) 2.316s (-38.4% 🟢) 0.364s 13 1.57x
💻 Local Next.js (Turbopack) 2.002s 2.591s 0.589s 12 1.62x
🌐 Redis Next.js (Turbopack) 2.352s 3.009s 0.657s 10 1.90x
🌐 MongoDB Next.js (Turbopack) 3.574s 4.008s 0.434s 8 2.88x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.459s (+7.0% 🔺) 5.514s (+8.6% 🔺) 2.055s 6 1.00x
▲ Vercel Next.js (Turbopack) 5.402s (+71.9% 🔺) 7.183s (+58.8% 🔺) 1.781s 5 1.56x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.391s (-60.0% 🟢) 2.008s (-49.9% 🟢) 0.617s 15 1.00x
🐘 Postgres Express 1.414s (-59.6% 🟢) 2.009s (-49.9% 🟢) 0.595s 15 1.02x
🐘 Postgres Next.js (Turbopack) 1.644s 2.007s 0.363s 15 1.18x
🌐 Redis Next.js (Turbopack) 3.588s 4.010s 0.422s 8 2.58x
💻 Local Nitro 4.907s (-46.3% 🟢) 5.347s (-46.7% 🟢) 0.440s 6 3.53x
💻 Local Next.js (Turbopack) 6.059s 6.618s 0.560s 5 4.35x
🌐 MongoDB Next.js (Turbopack) 6.300s 7.012s 0.713s 5 4.53x
💻 Local Express 6.668s (-24.2% 🟢) 7.020s (-24.3% 🟢) 0.352s 5 4.79x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 4.996s (-1.9%) 7.088s (+4.0%) 2.092s 5 1.00x
▲ Vercel Next.js (Turbopack) 5.910s (-12.5% 🟢) 8.572s (~) 2.661s 4 1.18x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.460s (-45.2% 🟢) 1.007s (-1.6%) 0.547s 60 1.00x
🐘 Postgres Nitro 0.469s (-42.9% 🟢) 1.007s (~) 0.538s 60 1.02x
💻 Local Nitro 0.470s (-52.1% 🟢) 1.004s (-8.2% 🟢) 0.534s 60 1.02x
💻 Local Express 0.478s (-51.4% 🟢) 1.004s (-6.7% 🟢) 0.526s 60 1.04x
🌐 Redis Next.js (Turbopack) 0.635s 1.004s 0.369s 60 1.38x
🐘 Postgres Next.js (Turbopack) 0.678s 1.006s 0.328s 60 1.47x
💻 Local Next.js (Turbopack) 0.731s 1.005s 0.274s 60 1.59x
🌐 MongoDB Next.js (Turbopack) 0.741s 1.005s 0.265s 60 1.61x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.164s (-76.6% 🟢) 7.195s (-70.1% 🟢) 2.032s 9 1.00x
▲ Vercel Next.js (Turbopack) 6.961s (-52.0% 🟢) 8.967s (-44.2% 🟢) 2.006s 7 1.35x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.086s (-45.0% 🟢) 1.845s (-18.3% 🟢) 0.758s 49 1.00x
🐘 Postgres Nitro 1.109s (-42.5% 🟢) 1.904s (-9.4% 🟢) 0.795s 48 1.02x
💻 Local Nitro 1.158s (-61.8% 🟢) 2.005s (-46.6% 🟢) 0.847s 45 1.07x
💻 Local Express 1.181s (-60.8% 🟢) 2.006s (-44.0% 🟢) 0.825s 45 1.09x
🌐 Redis Next.js (Turbopack) 1.503s 2.006s 0.502s 45 1.38x
🐘 Postgres Next.js (Turbopack) 1.619s 2.007s 0.389s 45 1.49x
💻 Local Next.js (Turbopack) 1.789s 2.006s 0.217s 45 1.65x
🌐 MongoDB Next.js (Turbopack) 1.812s 2.007s 0.196s 45 1.67x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 12.356s (-68.7% 🟢) 15.082s (-63.5% 🟢) 2.726s 6 1.00x
▲ Vercel Next.js (Turbopack) 16.164s (-67.5% 🟢) 18.071s (-65.1% 🟢) 1.907s 6 1.31x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.074s (-49.5% 🟢) 2.549s (-44.6% 🟢) 0.475s 48 1.00x
🐘 Postgres Express 2.090s (-47.6% 🟢) 2.639s (-39.6% 🟢) 0.549s 46 1.01x
💻 Local Nitro 2.642s (-71.6% 🟢) 3.033s (-69.7% 🟢) 0.390s 40 1.27x
💻 Local Express 2.668s (-71.0% 🟢) 3.008s (-70.0% 🟢) 0.339s 40 1.29x
🌐 Redis Next.js (Turbopack) 2.961s 3.032s 0.070s 40 1.43x
🐘 Postgres Next.js (Turbopack) 3.215s 4.011s 0.795s 30 1.55x
💻 Local Next.js (Turbopack) 3.803s 4.008s 0.205s 30 1.83x
🌐 MongoDB Next.js (Turbopack) 4.122s 5.012s 0.890s 24 1.99x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 37.971s (-60.8% 🟢) 40.686s (-58.7% 🟢) 2.715s 3 1.00x
▲ Vercel Next.js (Turbopack) 47.966s (-55.2% 🟢) 49.849s (-54.2% 🟢) 1.883s 3 1.26x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.195s (-31.1% 🟢) 1.006s (~) 0.811s 60 1.00x
🐘 Postgres Express 0.199s (-29.4% 🟢) 1.006s (~) 0.807s 60 1.02x
🐘 Postgres Next.js (Turbopack) 0.236s 1.006s 0.770s 60 1.21x
🌐 Redis Next.js (Turbopack) 0.237s 1.004s 0.767s 60 1.22x
💻 Local Express 0.451s (-19.6% 🟢) 1.005s (~) 0.554s 60 2.31x
💻 Local Nitro 0.457s (-24.4% 🟢) 1.021s (~) 0.564s 59 2.34x
💻 Local Next.js (Turbopack) 0.552s 1.005s 0.453s 60 2.83x
🌐 MongoDB Next.js (Turbopack) 1.032s 1.771s 0.739s 34 5.29x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.222s (+33.8% 🔺) 4.396s (+31.2% 🔺) 2.174s 14 1.00x
▲ Vercel Next.js (Turbopack) 4.556s (+125.3% 🔺) 6.597s (+73.9% 🔺) 2.040s 10 2.05x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.318s (-36.0% 🟢) 1.006s (~) 0.689s 90 1.00x
🐘 Postgres Express 0.338s (-33.7% 🟢) 1.007s (~) 0.669s 90 1.06x
🌐 Redis Next.js (Turbopack) 0.414s 1.004s 0.590s 90 1.30x
🐘 Postgres Next.js (Turbopack) 0.455s 1.006s 0.551s 90 1.43x
💻 Local Express 2.189s (-12.9% 🟢) 2.912s (-3.2%) 0.723s 31 6.89x
💻 Local Nitro 2.196s (-13.5% 🟢) 2.766s (-8.1% 🟢) 0.570s 33 6.91x
💻 Local Next.js (Turbopack) 2.522s 3.147s 0.626s 29 7.94x
🌐 MongoDB Next.js (Turbopack) 2.601s 3.006s 0.405s 30 8.19x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 6.842s (+93.5% 🔺) 9.053s (+74.3% 🔺) 2.211s 10 1.00x
▲ Vercel Nitro 6.990s (+116.7% 🔺) 9.389s (+94.7% 🔺) 2.399s 10 1.02x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.680s (-16.9% 🟢) 1.006s (-1.1%) 0.326s 120 1.00x
🐘 Postgres Nitro 0.683s (-13.6% 🟢) 1.007s (~) 0.324s 120 1.00x
🌐 Redis Next.js (Turbopack) 0.744s 1.004s 0.259s 120 1.09x
🐘 Postgres Next.js (Turbopack) 0.941s 1.219s 0.278s 99 1.38x
🌐 MongoDB Next.js (Turbopack) 5.381s 6.012s 0.631s 20 7.91x
💻 Local Nitro 9.836s (-12.1% 🟢) 10.360s (-11.2% 🟢) 0.524s 12 14.46x
💻 Local Express 10.243s (-8.5% 🟢) 10.777s (-9.7% 🟢) 0.534s 12 15.06x
💻 Local Next.js (Turbopack) 11.879s 12.531s 0.653s 10 17.47x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 18.627s (+80.4% 🔺) 20.323s (+65.4% 🔺) 1.695s 7 1.00x
▲ Vercel Nitro 27.519s (+256.3% 🔺) 30.173s (+221.0% 🔺) 2.654s 5 1.48x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack) | Nitro

Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.126s (+427.0% 🔺) 2.004s (+99.5% 🔺) 0.011s (-16.0% 🟢) 2.017s (+98.0% 🔺) 0.891s 10 1.00x
💻 Local Express 1.132s (+468.7% 🔺) 2.006s (+99.7% 🔺) 0.012s (-4.1%) 2.019s (+98.4% 🔺) 0.887s 10 1.01x
🐘 Postgres Nitro 1.137s (+454.9% 🔺) 1.995s (+99.6% 🔺) 0.001s (-20.0% 🟢) 2.010s (+98.7% 🔺) 0.872s 10 1.01x
🐘 Postgres Express 1.145s (+458.1% 🔺) 1.998s (+100.1% 🔺) 0.001s (-12.5% 🟢) 2.010s (+98.8% 🔺) 0.866s 10 1.02x
💻 Local Next.js (Turbopack) 1.174s 2.003s 0.013s 2.020s 0.846s 10 1.04x
🐘 Postgres Next.js (Turbopack) 1.204s 2.002s 0.002s 2.011s 0.807s 10 1.07x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.238s (-15.5% 🟢) 4.001s (-24.2% 🟢) 1.749s (+135.7% 🔺) 6.719s (+3.6%) 3.481s 10 1.00x
▲ Vercel Next.js (Turbopack) 5.152s (-24.8% 🟢) 4.976s (-42.5% 🟢) 0.938s (+48.5% 🔺) 7.843s (-19.9% 🟢) 2.691s 10 1.59x
▲ Vercel Express ⚠️ missing - - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.508s (+79.7% 🔺) 2.011s (+98.7% 🔺) 0.009s (-6.4% 🟢) 2.022s (+81.2% 🔺) 0.514s 30 1.00x
💻 Local Express 1.527s (+101.7% 🔺) 2.011s (+95.4% 🔺) 0.009s (+0.6%) 2.022s (+94.5% 🔺) 0.495s 30 1.01x
🐘 Postgres Nitro 1.540s (+146.8% 🔺) 2.007s (+99.4% 🔺) 0.004s (-8.1% 🟢) 2.025s (+98.1% 🔺) 0.485s 30 1.02x
🐘 Postgres Express 1.567s (+148.8% 🔺) 2.006s (+99.3% 🔺) 0.004s (-1.7%) 2.025s (+98.0% 🔺) 0.458s 30 1.04x
🐘 Postgres Next.js (Turbopack) 1.667s 2.009s 0.004s 2.026s 0.359s 30 1.11x
💻 Local Next.js (Turbopack) 1.683s 2.010s 0.011s 2.024s 0.341s 30 1.12x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 6.241s (-78.8% 🟢) 8.509s (-72.4% 🟢) 0.277s (+147.1% 🔺) 9.413s (-70.4% 🟢) 3.172s 7 1.00x
▲ Vercel Next.js (Turbopack) 12.727s (-24.8% 🟢) 13.763s (-24.5% 🟢) 0.211s (~) 15.275s (-19.3% 🟢) 2.548s 4 2.04x
▲ Vercel Express ⚠️ missing - - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.644s (-33.0% 🟢) 1.015s (-20.5% 🟢) 0.000s (-22.0% 🟢) 1.023s (-21.7% 🟢) 0.379s 59 1.00x
🐘 Postgres Nitro 0.676s (-30.2% 🟢) 1.052s (-15.7% 🟢) 0.000s (-57.9% 🟢) 1.060s (-15.7% 🟢) 0.384s 57 1.05x
🐘 Postgres Next.js (Turbopack) 0.787s 1.071s 0.000s 1.079s 0.292s 56 1.22x
💻 Local Express 1.352s (+10.4% 🔺) 2.016s (~) 0.000s (-60.0% 🟢) 2.018s (~) 0.666s 30 2.10x
💻 Local Nitro 1.363s (+11.4% 🔺) 2.015s (~) 0.000s (+166.7% 🔺) 2.018s (~) 0.655s 30 2.12x
💻 Local Next.js (Turbopack) 1.466s 2.014s 0.000s 2.017s 0.551s 30 2.28x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.253s (+6.7% 🔺) 4.858s (+10.6% 🔺) 0.000s (+18.2% 🔺) 5.510s (+14.6% 🔺) 2.256s 11 1.00x
▲ Vercel Next.js (Turbopack) 5.595s (-45.0% 🟢) 6.442s (-44.1% 🟢) 0.000s (NaN%) 7.673s (-36.3% 🟢) 2.078s 8 1.72x
▲ Vercel Express ⚠️ missing - - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.244s (-30.6% 🟢) 2.034s (-5.0% 🟢) 0.000s (-100.0% 🟢) 2.044s (-6.0% 🟢) 0.800s 30 1.00x
🐘 Postgres Express 1.418s (-20.0% 🟢) 2.101s (-3.5%) 0.000s (+Infinity% 🔺) 2.114s (-3.8%) 0.696s 29 1.14x
🐘 Postgres Next.js (Turbopack) 1.716s 2.261s 0.000s 2.273s 0.556s 27 1.38x
💻 Local Next.js (Turbopack) 2.801s 3.361s 0.000s 3.366s 0.565s 18 2.25x
💻 Local Nitro 3.080s (-9.1% 🟢) 3.778s (-6.3% 🟢) 0.001s (-6.2% 🟢) 3.781s (-6.3% 🟢) 0.701s 16 2.48x
💻 Local Express 3.121s (-10.0% 🟢) 3.967s (-1.7%) 0.001s (+9.4% 🔺) 3.969s (-1.7%) 0.849s 16 2.51x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.228s (+27.7% 🔺) 6.987s (+30.0% 🔺) 0.000s (-54.2% 🟢) 7.620s (+31.5% 🔺) 2.392s 8 1.00x
▲ Vercel Next.js (Turbopack) 10.107s (+79.9% 🔺) 10.260s (+47.0% 🔺) 0.000s (-100.0% 🟢) 12.147s (+61.1% 🔺) 2.040s 6 1.93x
▲ Vercel Express ⚠️ missing - - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Nitro 16/21
🐘 Postgres Nitro 12/21
▲ Vercel Nitro 18/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 16/21
Next.js (Turbopack) 🐘 Postgres 10/21
Nitro 🐘 Postgres 14/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)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: success
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 22, 2026

🧪 E2E Test Results

All tests passed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 925 0 219 1144
✅ 💻 Local Development 1237 0 219 1456
✅ 📦 Local Production 1237 0 219 1456
✅ 🐘 Local Postgres 1237 0 219 1456
✅ 🪟 Windows 104 0 0 104
✅ 📋 Other 552 0 176 728
Total 5292 0 1052 6344

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 78 0 26
✅ example 78 0 26
✅ express 78 0 26
✅ fastify 78 0 26
✅ hono 78 0 26
✅ nextjs-turbopack 102 0 2
✅ nextjs-webpack 102 0 2
✅ nitro 78 0 26
✅ nuxt 78 0 26
✅ sveltekit 97 0 7
✅ vite 78 0 26
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 79 0 25
✅ express-stable 79 0 25
✅ fastify-stable 79 0 25
✅ hono-stable 79 0 25
✅ nextjs-turbopack-canary 85 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 104 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 104 0 0
✅ nextjs-webpack-canary 85 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 104 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 104 0 0
✅ nitro-stable 79 0 25
✅ nuxt-stable 79 0 25
✅ sveltekit-stable 98 0 6
✅ vite-stable 79 0 25
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 79 0 25
✅ express-stable 79 0 25
✅ fastify-stable 79 0 25
✅ hono-stable 79 0 25
✅ nextjs-turbopack-canary 85 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 104 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 104 0 0
✅ nextjs-webpack-canary 85 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 104 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 104 0 0
✅ nitro-stable 79 0 25
✅ nuxt-stable 79 0 25
✅ sveltekit-stable 98 0 6
✅ vite-stable 79 0 25
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 79 0 25
✅ express-stable 79 0 25
✅ fastify-stable 79 0 25
✅ hono-stable 79 0 25
✅ nextjs-turbopack-canary 85 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 104 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 104 0 0
✅ nextjs-webpack-canary 85 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 104 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 104 0 0
✅ nitro-stable 79 0 25
✅ nuxt-stable 79 0 25
✅ sveltekit-stable 98 0 6
✅ vite-stable 79 0 25
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 104 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 79 0 25
✅ e2e-local-dev-tanstack-start- 79 0 25
✅ e2e-local-postgres-nest-stable 79 0 25
✅ e2e-local-postgres-tanstack-start- 79 0 25
✅ e2e-local-prod-nest-stable 79 0 25
✅ e2e-local-prod-tanstack-start- 79 0 25
✅ e2e-vercel-prod-tanstack-start 78 0 26

📋 View full workflow run

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a best-effort build-time warning in the builders layer when a user externalizes a package that appears to contain workflow constructs (step/workflow directives or serialization patterns), and documents the “externalization footgun” in the serialization guide.

Changes:

  • Add BaseBuilder.warnAboutExternalWorkflowPackages() and invoke it after the discovery phase.
  • Add Vitest coverage for the warning behavior across multiple detection paths and edge cases.
  • Document third-party package externalization pitfalls in the serialization guide, plus a changeset entry.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
packages/builders/src/base-builder.ts Implements external package workflow-pattern detection and emits warnings after discovery.
packages/builders/src/external-package-warning.test.ts Adds tests verifying warning emission and suppression behavior.
docs/content/docs/foundations/serialization.mdx Documents that workflow-exporting packages must not be externalized (e.g., Next.js serverExternalPackages).
.changeset/warn-external-workflow-packages.md Publishes a patch note for the new warning behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/builders/src/base-builder.ts
Comment thread packages/builders/src/base-builder.ts Outdated
Comment thread packages/builders/src/base-builder.ts
Comment thread packages/builders/src/base-builder.ts Outdated
VaguelySerious
VaguelySerious previously approved these changes Mar 23, 2026
Copy link
Copy Markdown
Contributor

@pranaygp pranaygp left a comment

Choose a reason for hiding this comment

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

Overall this is a well-scoped, useful addition. The detection logic is sound, tests are comprehensive, and the docs addition is clear. One real bug to fix (require.resolve in ESM) and a couple of minor suggestions.

Comment thread packages/builders/src/base-builder.ts
Comment thread packages/builders/src/base-builder.ts
Comment thread packages/builders/src/base-builder.ts Outdated
Comment thread packages/builders/src/base-builder.ts Outdated
Comment thread packages/builders/src/external-package-warning.test.ts
@pranaygp pranaygp dismissed VaguelySerious’s stale review March 23, 2026 20:28

real bug found by claude

Copy link
Copy Markdown
Contributor

@karthikscale3 karthikscale3 left a comment

Choose a reason for hiding this comment

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

Here's my review of PR #1481: Warn when serverExternalPackages hides workflow-enabled packages.


Summary

This PR adds a build-time warning when packages listed in serverExternalPackages (or externalPackages) contain workflow code that would be invisible to the workflow compiler. It also adds documentation in the serialization guide explaining the externalization footgun. The problem is well-motivated -- externalized packages silently fail at runtime because the SWC transform never runs on them.

Blocker: require.resolve in ESM context

Both Copilot and @pranaygp already flagged this, and I agree -- this is a real bug that needs fixing before merge.

@workflow/builders is "type": "module", so bare require.resolve() will throw ReferenceError: require is not defined at runtime. The tests pass because Vitest transpiles to CJS, masking the issue. Both require.resolve() calls (lines 188 and 207) are wrapped in try/catch, so the warning logic will silently never run in production.

The fix is straightforward -- the codebase already has the pattern in apply-swc-transform.ts:

import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);

Add the same to base-builder.ts (or scope it inside the method).

No Other Blockers

Beyond the ESM bug, the implementation is solid.

Code Review Details

Detection logic is well-designed

The two-tier approach is smart:

  1. Fast path: Check package.json for @workflow/serde in deps/peerDeps
  2. Thorough path: Read the entry file and run detectWorkflowPatterns() (reusing existing regex-based detection from transform-utils.ts)

The triple try/catch nesting (outer best-effort, inner fast-path, inner thorough-path) ensures the warning never blocks a build regardless of what goes wrong.

Warning message is actionable

The warning clearly identifies: which package, what was found (step functions, workflow functions, serialization classes), and exactly how to fix it (Remove "pkg" from serverExternalPackages).

Minor issues flagged in existing reviews (agree with all)

  1. warnedExternalPackages.add(pkg) placement -- The package is added to the dedup set before checking if a warning fires. If detection fails (e.g., partial install), the package is permanently skipped for this builder instance. Since builders are short-lived, this is low-impact, but moving .add(pkg) to just before console.warn() would be more correct.

  2. Hard-coded serverExternalPackages in warning text -- The config field is externalPackages. Non-Next.js users (Astro, SvelteKit, etc.) may be confused by the Next.js-specific name. @pranaygp's suggestion to say externalPackages (serverExternalPackages in Next.js) is good.

  3. Entry-point-only detection -- require.resolve(pkg) only reads the main entry file. If workflow constructs are in sub-paths (e.g., my-pkg/workflows), regex detection misses them. The @workflow/serde dep check partially covers serde cases. This is acceptable as a best-effort heuristic -- worth a code comment.

Tests are comprehensive (337 lines, 9 tests)

Covers: serde dep detection, serde symbol detection, "use step", "use workflow", combined patterns, pseudo-package skipping, clean packages, dedup behavior, and @workflow/serde in peerDependencies. One gap noted by @pranaygp: no test where package.json resolution fails but entry-file detection succeeds (testing that fast-path failure doesn't block thorough-path).

Documentation addition is clear

The new "Third-Party Packages" section in docs/content/docs/foundations/serialization.mdx explains the problem, shows the warning message, and provides the fix. Good placement.

Changeset is correct

Only @workflow/builders is listed as patch -- appropriate since the warning is contained within that package.

CI Status

4 failures, none related:

  • Benchmark Vercel (express), Benchmark Local (nitro-v3), Benchmark Community World (MongoDB) -- infra timeouts (1h+)
  • Vercel workbench-nestjs-workflow deployment -- pre-existing NestJS deployment issue (see PR #1485)

All E2E tests pass (780 Vercel prod, 782 local dev/prod/postgres, 72 Windows). Community world failures are the usual turso/redis/mongodb pre-existing issues.

Verdict: Fix the require.resolve ESM bug (use createRequire), then ship it. The minor suggestions (dedup timing, warning text, code comment) are nice-to-haves.

Comment thread docs/content/docs/foundations/serialization.mdx Outdated
pranaygp pushed a commit to vercel/sandbox that referenced this pull request Mar 27, 2026
…ibility (#109)

## Summary

Makes `@vercel/sandbox` fully compatible with the Workflow DevKit
compiler so that `Sandbox`, `Command`, and `CommandFinished` instances
can be used directly inside `"use workflow"` functions — no wrapper step
functions needed.

Supersedes #58.

## Problem

When `@vercel/sandbox` is imported in a workflow context, the workflow
builder tries to bundle it into the workflow VM bundle (because it has
`WORKFLOW_SERIALIZE`/`WORKFLOW_DESERIALIZE` on its classes). This fails
because:

1. The SDK's public methods use Node.js APIs (`fs`, `stream`, `zlib`,
`undici`, etc.) which are forbidden in the workflow VM
2. The compiled `dist/index.js` was a single bundled file that hoisted
all Node.js imports to the top, making them impossible to tree-shake
3. The `Sandbox` and `Command` classes had a sync `client` getter that
directly referenced `APIClient`, pulling the entire HTTP client stack
into the module scope

## Solution

### 1. `"use step"` annotations on all public async methods

Added `"use step"` to all 22 public async methods across `Sandbox` (14),
`Command` (5), and `Snapshot` (3). The SWC plugin strips these method
bodies in workflow mode, replacing them with durable step proxies. This
eliminates all Node.js API references from the workflow bundle.

### 2. Async `ensureClient()` replaces sync `client` getter

The sync `get client()` getter directly referenced `APIClient`, which
pulls in `undici`, `zlib`, `tar-stream`, `jsonlines`, etc. Replaced
with:

```typescript
private async ensureClient(): Promise<APIClient> {
  "use step";
  if (this._client) return this._client;
  const credentials = getSandboxCredentials();
  this._client = new APIClient({ ... });
  return this._client;
}
```

Since `ensureClient()` is itself `"use step"`, its body (including `new
APIClient(...)`) gets stripped in workflow mode. All instance methods
now call `const client = await this.ensureClient();` instead of
`this.client`.

### 3. `bundle: false` in tsdown config

Changed from single-file bundling to per-file output. This keeps Node.js
imports local to the files that use them, so after the SWC plugin strips
step method bodies, the now-unused Node.js imports can be eliminated by
esbuild's tree-shaking.

### 4. Workflow-code-runner example updated

- Removed `serverExternalPackages: ["@vercel/sandbox"]` from
`next.config.ts` (no longer needed)
- Updated `workflow` dependency to use a tarball that includes the
inline class serialization registration fix (vercel/workflow#1480)

## Testing

- `pnpm build` succeeds for the full monorepo (all 8 tasks)
- The `workflow-code-runner` example app builds successfully with all
workflow routes generated
- 22 `"use step"` directives survive compilation in both ESM and CJS
dist output

## Related

- vercel/workflow#1480 — Inline class serialization registration (fixes
SWC import resolution for 3rd-party packages)
- vercel/workflow#1481 — Build-time warning when
`serverExternalPackages` hides workflow-enabled packages
- vercel/workflow#1144 — CJS detection for serde symbols (pending
review)
- #58 — Previous attempt (superseded by this PR)

---------

Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
@TooTallNate TooTallNate requested a review from ijjk as a code owner April 6, 2026 18:37
Add a build-time warning when packages in serverExternalPackages contain
workflow code ('use step', 'use workflow', or serialization classes).
These packages are completely invisible to the workflow compiler when
externalized, causing silent runtime failures.

The warning detects workflow patterns via two methods:
- Fast path: check package.json dependencies for @workflow/serde
- Thorough path: read the package entry file and run pattern detection

Also adds documentation in the serialization guide about the
externalization footgun for 3rd-party packages.
When workflow-enabled dependencies are externalized in Next.js, compiler transforms are skipped and runtime failures follow. Detect those packages in withWorkflow, remove them from serverExternalPackages for the current build, and keep a generalized externalPackages warning fallback for non-Next builders.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Backport PR opened against stable: #1940. Merge conflicts were resolved by AI — please review carefully.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants