Skip to content

Build2 publishers3 min readPublished

Vercel Labs' ScriptC trades sustained TypeScript throughput for millisecond startups

Vercel Labs' experimental ScriptC compiles TypeScript to native binaries that started in 1.78ms against Node's 61.78ms in one benchmark. The gain holds for short, statically typed programs, while framework code and sustained compute both ran slower than Bun or Node.

The Engineer · Build desk

Illustration accompanying Vercel Labs' ScriptC trades sustained TypeScript throughput for millisecond startups

What happened

  • Code lands in one of three tiers: compiled statically by default, run in an embedded quickjs-ng engine of about 620KB under --dynamic, or rejected at compile time with an SC code and a rewrite hint.
  • The Hono framework needed --dynamic in the same tests, pushing 62% of the server into QuickJS and cutting throughput to 18.4k requests per second against Bun's 70.5k.
  • The documented divergences from Node include UTF-8 strings, reference counting instead of garbage collection, Object.keys in declaration order, and process.argv[0] returning "scriptc".
  • The vercel-labs/scriptc repository was created on 22 July 2026 and has gathered around 4.9k stars.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • decision Moving a Hono service to scriptc today would give up roughly three quarters of Bun's throughput to save startup time that a long-running server pays once.
  • constraint Before any of the startup numbers apply, a team has to move any-typed and framework code into the static tier, so adoption begins as a rewrite.
  • exposure Programs that read Object.keys order or print process.argv[0] can produce different output compiled than they do under Node.
  • cost Commenters cited Vercel's zerolang, whose commits stopped weeks after launch, so a team shipping on scriptc carries the cost of a fork if this project stalls too.

`scriptc build` passes the source to the official TypeScript compiler for parsing and type checking, lowers the checked program to a typed IR, and then emits C, LLVM IR, assembly, object files or WebAssembly for WASI Preview 1 [3]. Each stage can be dumped with `--emit` [3]. A fully static build still links a small runtime pack for memory management and core primitives [4]. The binary has no Node, no V8 and no JIT [4]. A `node:http` server compiles to an event loop that calls native socket primitives directly [5].

In the benchmark InfoQ reported, scriptc 0.0.16 started a CLI about 12 times faster than Bun 1.3.12 and about 35 times faster than Node 24.18.0 [10]. A framework-free `node:http` server idled at 1.9MiB [8]. For those numbers to transfer to another workload, two things have to be true: the program has to compile without `--dynamic`, and most of its run time has to be startup.

One developer on Hacker News tested the second condition. A byte-array workload ran about 7.5 times slower than Node 24, while the 370KB executable started in 1.5ms against 18.6ms and used 2.5MiB against 181MiB [12]. The dev.to write-up puts typical single-binary bundles at 40MB to 90MB, because they carry V8 and the Node runtime [1]. On the developer's figures, the binary saves 17.1ms at startup and spends 6.5 extra milliseconds for every millisecond Node spends working. It comes out ahead only when the Node version does under about 2.6ms of work per run [13]. That assumes the slowdown applies evenly to everything after startup [13]. Remo Jansen's attempt to compile the TypeScript 6 compiler failed at an internal compiler error. He measured a 3.6ms cold start against Node's 48.9ms and a much slower 2.33s on compute, according to InfoQ [15].

Filip Pizlo said treating every number as a float and deferring integer inference skips "half the problem of fast JS" [14]. He also argued that `any` is common enough that real programs will keep falling into the QuickJS fallback [14]. `scriptc coverage app.ts` is the check to run first. It reports what share of the AST compiles statically and gives a diagnostic code for each dynamic site [16]. One developer ran it on every local project and wrote: "For every single of them the coverage generates hundreds of errors and so it is basically useless." [17] An experimental `--npm-static` flag compiles named packages out of the fallback [20].

The testing discipline deserves credit. scriptc targets macOS, Linux, Windows and WASI Preview 1, and differential tests compare stdout, stderr and exit codes byte for byte against Node [19]. Because the compiler emits readable C, anyone doubting a performance claim can read the code it actually produced [3].

Simon Willison said building small, fast binaries without writing C or Rust "seems like a valuable capability" [23]. I agree, for a narrow case. In my view the first candidate is an internal CLI with no npm dependencies and a clean coverage report. Where an edge target needs it, that CLI can be cross-compiled to wasm32-wasi with Zig as the linker driver [25]. The compiler driver itself needs Node.js 24 or newer [24], so the build machine keeps the runtime the binary leaves out.

What to watch

  • Whether a later scriptc release adds integer inference, the gap Filip Pizlo named, and whether the 7.5x byte-array slowdown narrows as a result.
  • Whether --npm-static can compile a framework like Hono out of QuickJS and bring its throughput near Bun's 70.5k requests per second.
  • Commit activity on vercel-labs/scriptc over the coming months, measured against the zerolang precedent commenters raised.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories