Back to BlogNode.js

Bun 1.3.14: Native Image Processing and HTTP/3 Without the Setup Tax

Bun 1.3.14 ships Bun.Image for zero-dependency server-side image processing and experimental HTTP/3 via QUIC in Bun.serve(). Walkthrough of both APIs, where Bun.Image falls short versus sharp, and what experimental HTTP/3 means for local development.

bunhttp3quicimage-processingjavascriptperformance
Bun 1.3.14: Native Image Processing and HTTP/3 Without the Setup Tax

Bun 1.3.14 Shipped Two Features Worth Paying Attention To

Bun 1.3.14 landed on July 8 with the usual patch cadence: 92 bugs closed, Node.js compatibility improved, binaries shrunk. But Bun.Image for built-in server-side image processing and HTTP/3 (QUIC) support in Bun.serve() are different in kind from patch fixes. Both change what you need to reach for when building a backend.

The Sharp Tax Is Real

If you've set up a Node.js image pipeline before, you know the tax. npm install sharp triggers node-gyp, which needs Python, which needs a C++ compiler, which fails on CI runners without libvips pre-installed. Sharp is fast and covers the common transforms. But the install footprint is 70MB+ with platform binaries, and a fresh CI cache miss can cost 3-4 minutes just for a resize call.

// Node.js + sharp: the old way
import sharp from 'sharp';

const resized = await sharp('./input.jpg')
  .resize(800, 600)
  .webp({ quality: 80 })
  .toBuffer();

Works fine until your CI runner is missing libvips.

Bun.Image: Zero Dependencies, Same API Shape

Bun.Image is built directly into the runtime. No install, no node-gyp, no libvips as a system dependency.

// Bun 1.3.14+
const file = Bun.file('./input.jpg');
const image = await Bun.Image.decode(await file.arrayBuffer());

const resized = image.resize(800, 600);
const out = await resized.encode('webp', { quality: 80 });

await Bun.write('./output.webp', out);

The API is async-first and works with ArrayBuffer, which fits a Bun.serve() handler without any adaptation:

Bun.serve({
  port: 3000,
  async fetch(req) {
    if (req.method !== 'POST') return new Response('Method not allowed', { status: 405 });

    const buffer = await req.arrayBuffer();
    const image = await Bun.Image.decode(buffer);
    const thumb = image.resize(320, 240);
    const out = await thumb.encode('webp', { quality: 75 });

    return new Response(out, {
      headers: { 'Content-Type': 'image/webp' },
    });
  },
});

No dependencies. No build step. Works on macOS, Linux, Windows, FreeBSD, and Android.

Bun.Image doesn't match sharp feature-for-feature yet. Resize, crop, rotate, and format conversion (JPEG, PNG, WebP, AVIF) work. Compositing, tinting, and raw pixel operations are missing. For thumbnail generation and upload pipelines, coverage is solid. For image editing pipelines, you'll still need sharp or a wasm fallback.

HTTP/3 in Bun.serve()

HTTP/3 runs over QUIC instead of TCP. The practical benefits are narrow but real: mobile clients switching networks keep their connections alive (QUIC ties to a connection ID rather than an IP), high-latency links benefit from 0-RTT on reconnects, and heavily multiplexed traffic avoids head-of-line blocking. CDNs have deployed HTTP/3 for years. Getting it working at the origin has meant nginx with a custom QUIC build or h3 libraries bolted onto Node.js.

Bun 1.3.14 adds it to Bun.serve() behind a single flag:

Bun.serve({
  port: 443,
  tls: {
    cert: Bun.file('./cert.pem'),
    key: Bun.file('./key.pem'),
  },
  http3: true,
  fetch(req) {
    return new Response('Hello from HTTP/3!');
  },
});

The server negotiates HTTP/3 via ALPN and falls back to HTTP/2 or HTTP/1.1 for clients that don't support it, with no additional packages required.

The experimental label is honest: server push isn't implemented yet, and large request bodies hit edge cases. Don't deploy this in production. For local development and benchmarking against an HTTP/3 origin without standing up nginx, it works today.

The QUIC handshake folds TLS 1.3 into the transport layer. On reconnects, browsers send application data in the first packet. HTTP/2 over TLS needs 2-3 round trips before application data moves.

Faster Warm Installs

The third headline in 1.3.14 is 7x faster warm installs from the isolated linker's global store. Bun now maintains a content-addressed global cache, and warm installs hardlink from it rather than re-extracting. On large lockfiles in CI, the savings compound with your existing cache layer:

$ bun install  # first run
[+] 284 packages installed in 1.8s

$ bun install  # warm run (same lockfile)
[+] 284 packages (up to date) in 0.24s

Should You Upgrade?

If you're on Bun, upgrade. Bun.Image and the install improvements alone justify it.

If you're on Node.js and Bun.Image sounds useful, prototype a simple image resizing service against it. The zero-dependency story holds in practice, and the API surface is close enough to sharp for common use cases that a migration is straightforward.

HTTP/3 is a one-boolean switch in your serve config, so there's no reason not to try it locally. The fact that a runtime now gives you HTTP/3 with zero setup cost is the real story. Bun's batteries-included approach keeps shipping things that used to require a separate library or a custom-built server binary. This release is a good example of that direction.