Vercel — Inside Workflow DevKit: How framework integrations work¶
Summary¶
Vercel's 2026-04-21 engineering post explains the integration
pattern behind the Workflow
Development Kit (WDK) — how one workflow-definition
artefact (code with "use workflow" / "use step"
directives) gets compiled and hosted across eight
frameworks (Next.js, Nitro,
SvelteKit, Astro,
Express, Hono, plus
TanStack Start and React Router in development) without
emulation or framework-specific SDKs per framework. Load-
bearing thesis verbatim: "What looks like six different
problems is really one problem solved six different ways."
The integration follows a two-phase pattern every framework implements:
- Build-time phase — compile workflow + step functions into executable handler files, determine output location, apply framework-specific patches, configure HMR.
- Runtime phase — apply workflow-client transforms, expose handler files as HTTP endpoints reachable by the app's server.
The "magic" sits in WDK's SWC compiler plugin, which transforms the same input file into three different outputs depending on mode:
- Client mode runs during the framework's build via a
Rollup or Vite plugin — transforms workflow calls into HTTP
client code and attaches
workflowIdproperties. - Step mode runs during WDK's esbuild phase — transforms
"use step"functions into HTTP handlers that execute step logic on the server. - Workflow mode also runs during esbuild — transforms
"use workflow"functions into orchestrators that run in a "sandboxed virtual environment."
"You write your code once, and the compiler generates the client, step handler, and workflow handler automatically."
The post walks the SvelteKit integration as the worked
example — one import line enables WDK, and behind it
workflowPlugin() runs two things in parallel: (A) client
transformation via @workflow/rollup's
workflowTransformPlugin() hooked into Vite's build in
mode: 'client', adding id to workflow imports; (B) handler
generation via SvelteKitBuilder producing two esbuild
bundles (mode: 'step' + mode: 'workflow') that become the
+server.js files in src/routes/.well-known/workflow/v1.
SvelteKit's file-based routing then auto-exposes them as HTTP
endpoints.
Frameworks split into two integration families:
- File-based routing frameworks (Next.js, SvelteKit,
Nuxt) — integration is "straightforward": output handler
files into framework-specific directories
(
app/.well-known/workflow/v1for Next.js,src/routes/.well-known/workflow/v1for SvelteKit), and the framework auto-discovers them as HTTP endpoints. Each still needs framework-specific patches for how endpoints are defined and handled. - HTTP server frameworks (Express, Hono) — "don't ship with a build system" (no bundler, bare HTTP server). WDK uses Nitro here: esbuild bundles the workflows, Nitro mounts them as virtual handlers; at runtime Nitro wraps the user's HTTP server and injects the virtual handlers to expose workflow endpoints.
Framework-specific adaptation disclosed in detail: SvelteKit passes a custom request object to route handlers, but workflow handlers expect the standard Web Request API. WDK solves this by injecting a small converter function into each generated handler:
async function convertSvelteKitRequest(request) {
method: request.method,
headers: new Headers(request.headers)
if (!['GET', 'HEAD'].includes(request.method)) {
options.body = await request.arrayBuffer();
return new Request(request.url, options);
HMR integration disclosed via Vite's hotUpdate hook:
async hotUpdate({ file, read }) {
const content = await read();
const useWorkflowPattern = /^\s*(['"])use workflow\1;?\s*$/m;
const useStepPattern = /^\s*(['"])use step\1;?\s*$/m;
if (!useWorkflowPattern.test(content) && !useStepPattern.test(content)) {
await enqueue(() => builder.build());
Save a workflow file → Vite fires hotUpdate → WDK checks
for "use workflow" / "use step" directives → triggers an
esbuild rebuild. "No manual restart required."
"Many modern frameworks are built on top of Vite (SvelteKit, Astro, Nuxt). This meant most of the integration code for plugin registration, HMR configuration, and client transforms was nearly identical across them. We built the core Vite integration once, then adapted it for each framework's specific routing patterns."
Adoption claim: >1,300 GitHub stars since launch, "thousands of developers" on SvelteKit alone; six new integrations since launch (plus the launch pair Next.js + Nitro).
Key takeaways¶
-
A workflow runtime can be onboarded to any JS framework via a two-phase pattern: build-time handler generation + runtime handler exposure. WDK productises this split as a reusable integration template — SvelteKit, Astro, Next.js, Nuxt instances all implement the same two phases with framework-specific adapters. Canonical statement of the patterns/two-phase-framework-integration-pattern. Verbatim: "On the surface, integrating WDK with Next.js looks nothing like integrating it with Express or SvelteKit. They all have different bundlers, routing systems, and developer experiences. But at its core, every framework integration follows the same two-phase pattern." (Source: this ingest)
-
One SWC compiler plugin emits three different outputs from the same input file by mode axis. Client, Step, Workflow modes take the same
"use workflow"/"use step"source and rewrite it into HTTP-client code / step HTTP-handler code / sandboxed-orchestrator code respectively. Canonical instance of patterns/swc-plugin-three-mode-transform — a compiler plugin parameterised by mode so user code stays single-artefact while deployment artefacts triplicate. Verbatim: "the magic happens in WDK's SWC compiler plugin, which transforms the same input file into three different outputs depending on the mode." (Source: this ingest) -
"use workflow"and"use step"are compilation markers — string directives the compiler matches to decide the transform path. Same structural mechanism as React's"use client"/"use server": a string literal at the top of a function body flags it for special compiler treatment. Regex from the VitehotUpdatehook made explicit:/^\s*(['"])use workflow\1;?\s*$/m. Canonical instance of concepts/use-directive-as-compilation-marker. Generalises beyond React-land: any framework that does compile-time split can adopt the directive-string idiom. (Source: this ingest) -
Frameworks without a bundler need a shim bundler + virtual handler layer — Nitro is WDK's choice. Express and Hono "don't ship with a build system" — they expose a bare HTTP server. WDK bundles workflows with esbuild then uses Nitro to mount them as virtual handlers injected into the user's HTTP server at runtime. Canonical instance of patterns/virtual-handler-via-nitro-for-bundlerless-frameworks. Verbatim: "For frameworks without a bundler like Express or Hono, we use Nitro instead. Nitro is a server toolkit that provides file-based routing, a build system, and other quality-of-life features such as virtual handlers that can be mounted to a server at runtime." (Source: this ingest)
-
Framework-specific request-object shapes get bridged by a small injected converter function per handler, not by a global adapter layer. Workflow handlers expect the standard Web Request API; SvelteKit (and presumably others) pass a custom object. WDK injects a
convertSvelteKitRequesthelper into each generated handler rather than building a fat cross-framework abstraction. Canonical instance of patterns/injected-request-object-converter. Verbatim: "different frameworks have different opinions on what a 'request' looks like ... We fixed this by injecting a small converter function into each generated handler." (Source: this ingest) -
HMR integration rides on Vite's
hotUpdatehook with a directive-regex filter gating esbuild rebuilds. Canonical instance of patterns/vite-hotupdate-directive-triggered-rebuild.async hotUpdate({ file, read })→ read content → regex-test foruse workflow/use step→ if match, enqueuebuilder.build(). Cheap directive test in front of expensive rebuild = HMR stays fast. (Source: this ingest) -
Framework taxonomy bifurcates on whether the framework ships with file-based routing vs bare HTTP server. Canonical instance of concepts/file-based-routing-vs-bare-http-framework-taxonomy. File-based-routing frameworks (Next.js, SvelteKit, Nuxt) give you "output a file into the right directory" as the integration seam; HTTP-server frameworks (Express, Hono) give you "inject handlers into the running server" instead — and the Nitro-based shim backfills the file-based-routing seam for them. (Source: this ingest)
-
Build-time vs runtime phase separation is the load-bearing decomposition axis for a compiler-centric framework integration. Canonical instance of concepts/build-time-vs-runtime-phase-separation. One team, one plugin codepath, two temporal layers: (A) build-time compiles source → deployable handler artefacts; (B) runtime wires those artefacts into the framework's HTTP surface. Thinking in these two layers keeps framework integrations comparable and reusable; thinking monolithic ("how do I make this work with SvelteKit") produces non-transferable code. (Source: this ingest)
-
Vite-based frameworks get the integration mostly for free — one core Vite integration, then per-framework adapter for routing patterns. Verbatim: "we built the core Vite integration once, then adapted it for each framework's specific routing patterns." Astro + SvelteKit
-
Nuxt share ~90% of the WDK integration code because they share Vite's plugin system + HMR + file-based routing. Compilation of framework-agnostic primitives (Vite) amortises integration cost across the Vite ecosystem. (Source: this ingest)
-
Launch-post scope-override rationale. Despite marketing-voice framing and a product-adoption brag ("1,300 GitHub stars"), the body discloses four named mechanisms (SWC three-mode plugin, Nitro-virtual-handler for bundlerless frameworks, request-converter injection, Vite-hotUpdate-gated-rebuild) and one architectural taxonomy (file-based-routing vs bare-HTTP frameworks) that were absent from prior wiki coverage of framework integration. The post's load-bearing thesis "one problem solved six different ways" is a compression of the patterns/two-phase-framework-integration-pattern idea into one quotable line. (Source: this ingest)
Systems, concepts, patterns extracted¶
Systems named¶
- Vercel Workflow (WDK) — the durable-workflow primitive whose integration pattern is the subject.
- Next.js — one of two launch-supported frameworks.
- Nitro (UnJS) — new to wiki; the UnJS server toolkit used as integration substrate for bundlerless frameworks. Disambiguated from AWS Nitro.
- SvelteKit — new to wiki; the worked-example framework for the post.
- Astro — Vite-based framework; "nearly identical" integration to SvelteKit.
- Express — new to wiki; bundlerless framework needing Nitro shim.
- Hono — bundlerless framework needing Nitro shim.
- Vite — build tool powering SvelteKit / Astro / Nuxt integration.
- Rollup — production bundler under Vite
7;
@workflow/rollupis the package name. - esbuild — new to wiki; WDK uses it for the step + workflow handler bundles.
Concepts canonicalised (new)¶
- concepts/use-directive-as-compilation-marker — string
literal at top of function as compile-time flag;
"use workflow"/"use step"family paralleling React's"use client"/"use server". - concepts/build-time-vs-runtime-phase-separation — the load-bearing decomposition axis for compiler-centric framework integration.
- concepts/file-based-routing-vs-bare-http-framework-taxonomy — the framework bifurcation axis that determines integration shape (file-output vs handler-injection).
Patterns canonicalised (new)¶
- patterns/two-phase-framework-integration-pattern — the reusable template: build-time handler generation + runtime handler exposure, with per-framework adapters for the bridge points.
- patterns/swc-plugin-three-mode-transform — one compiler plugin emits N outputs from one input by mode axis.
- patterns/virtual-handler-via-nitro-for-bundlerless-frameworks — substrate shim for frameworks that don't ship with a build system.
- patterns/injected-request-object-converter — per-handler converter over cross-framework abstraction layer.
- patterns/vite-hotupdate-directive-triggered-rebuild — gate expensive rebuild on cheap directive-regex test.
Operational numbers disclosed¶
- 8 frameworks supported as of 2026-04-21 (Next.js, Nitro, SvelteKit, Astro, Express, Hono, +2 others named in-post).
- 2 frameworks in active development (TanStack Start, React Router).
- >1,300 GitHub stars since launch (~1 month prior).
- "Thousands of developers" on SvelteKit alone (marketing-adjacent).
- 6 new integrations since launch (launch pair was Next.js + Nitro).
- Generated handler path (file-based-routing frameworks):
- Next.js:
app/.well-known/workflow/v1 - SvelteKit:
src/routes/.well-known/workflow/v1 - Integration code footprint estimate (implicit): one
workflowPlugin()function +SvelteKitBuilderclass + one converter function per framework.
Caveats¶
- Launch-voice post with adoption brag. The opening framing ("We wanted it to reflect our Open SDK Strategy") and closing metrics ("1,300 GitHub stars", "goal ... language-level concept") are marketing register; the architecture content is real but sandwiched in product-framing.
- Mechanism depth on three SWC transform modes not
walked. The post names Client / Step / Workflow modes
and their roles, but doesn't walk the actual AST
transformations. What does
"use step"→ HTTP handler compile to? What does"use workflow"→ orchestrator look like? Sandbox shape for workflow orchestrators undocumented. - "Sandboxed virtual environment" for workflow orchestrators is asserted, not described. No disclosure of isolate model, determinism invariants, replay semantics, deterministic-function requirements — this is the hard-reliability core of any durable workflow system and it's elided.
- Nitro integration mechanism thin. "Nitro wraps your HTTP server and injects the virtual handlers" — but how? What does the Express / Hono app author see? Is Nitro in the user's dependency tree or is it a build-time-only tool? Runtime behaviour underdocumented.
- HMR hook code snippet is partial. The
hotUpdatesnippet shows the directive-match path but not the rebuild cycle (enqueue semantics, rebuild idempotency, incremental-rebuild-vs-full-rebuild) or error recovery on a broken rebuild. - Request-converter snippet is incomplete JS. Missing
function body braces and
const options = {line — reads as pedagogy-altitude snippet, not directly executable. Presumably redacted for length; full converter code would live in the SvelteKit integration repo. - No production numbers. Zero latency / throughput / cold-start / build-time / HMR-latency data. Adoption numbers are ecosystem-count not performance.
- No competitor comparison. Inngest / Trigger.dev / Temporal frameworks ship similar multi-framework integration stories; unengaged.
react-dom/serverparallel named in prior Vercel Bun post (2026-04-21), not this one — the integration pattern here doesn't touch the runtime / performance axis.- Unsigned Vercel default attribution.
Cross-source continuity¶
- Fifth 2026-04-21 Vercel ingest, adding to the same-day launch cluster: 2026-04-21 BotID Deep Analysis + Bun runtime + Knowledge Agent Template + Chat SDK + this post. Four of the five required scope-override or batch-skip-override decisions. The Vercel launch-voice = skip heuristic has now been overridden five times on the same day's posts — signal that the Vercel blog's launch voice is a weak classifier for architectural-content presence, and mechanism-density inspection should be the default.
- Extends the Workflow DevKit coverage that the prior [[sources/2026-04-21-vercel-build-knowledge-agents-without-embeddings|2026-04-21 Knowledge Agent Template]] ingest anchored with a one-line mention ("content syncs to a snapshot repository via Vercel Workflow") — this post is the mechanism walk behind that one-liner for the integration-pattern axis. The Knowledge Agent Template post canonicalised Workflow-as-orchestrator at use-case altitude; this post canonicalises Workflow-as-multi-framework-SDK at integration-pattern altitude.
- Sibling to Cloudflare's vinext 2026-02-24 post — both engage the framework-integration-as-platform-concern problem at different altitudes: vinext rebuilds a framework from scratch on Vite as the unification strategy; WDK stays at SDK layer and pushes the cross-framework reuse into a compiler plugin + core Vite integration. The two posts bracket the framework-lock-in problem space — rebuild vs. wrap.
- Related to patterns/clean-reimplementation-over-adapter at the inverse altitude — Cloudflare's vinext chose clean reimplementation over adapter; WDK chose shared-compiler- plugin-plus-per-framework-adapter. The two choices illustrate that the adapter pattern is viable when the compile-time abstraction (SWC plugin + Vite integration) is strong enough to amortise the per-framework adapter cost.
- No existing-claim contradictions — strictly additive.
Source¶
- Original: https://vercel.com/blog/inside-workflow-devkit-how-framework-integrations-work
- Raw markdown:
raw/vercel/2026-04-21-inside-workflow-devkit-how-framework-integrations-work-d02ebe93.md
Related¶
- companies/vercel
- systems/vercel-workflow
- systems/nitro-unjs
- systems/sveltekit
- systems/astro
- systems/express
- systems/hono
- systems/vite
- systems/esbuild
- systems/nextjs
- concepts/use-directive-as-compilation-marker
- concepts/build-time-vs-runtime-phase-separation
- concepts/file-based-routing-vs-bare-http-framework-taxonomy
- patterns/two-phase-framework-integration-pattern
- patterns/swc-plugin-three-mode-transform
- patterns/virtual-handler-via-nitro-for-bundlerless-frameworks
- patterns/injected-request-object-converter
- patterns/vite-hotupdate-directive-triggered-rebuild