Skip to main content

unenv vs edge-runtime vs @cloudflare/workers-types: Edge Environment Polyfills (2026)

·PkgPulse Team

TL;DR

unenv is the UnJS environment polyfill layer — provides Node.js API polyfills for edge/browser runtimes, powers Nitro's cross-platform deployment, converts Node.js code to run anywhere. edge-runtime is Vercel's edge runtime emulator — local development environment matching Vercel Edge Functions, tests edge code locally, WinterCG compatible. @cloudflare/workers-types provides TypeScript types for Cloudflare Workers — types for KV, R2, D1, Durable Objects, and Workers runtime APIs. In 2026: unenv for universal polyfills, edge-runtime for Vercel edge testing, @cloudflare/workers-types for Cloudflare Workers TypeScript.

Key Takeaways

  • unenv: ~5M weekly downloads — UnJS, Node.js polyfills for edge, powers Nitro
  • edge-runtime: ~1M weekly downloads — Vercel, local edge emulator, WinterCG
  • @cloudflare/workers-types: ~500K weekly downloads — TypeScript types for Workers APIs
  • Different purposes: polyfill (unenv), emulate (edge-runtime), type (workers-types)
  • unenv makes Node.js code portable to edge environments
  • edge-runtime lets you test Vercel Edge Functions locally

unenv

unenv — universal environment polyfills:

What it does

unenv converts Node.js built-in modules to work in non-Node environments:

Node.js module  →  unenv polyfill
─────────────────────────────────────
node:buffer     →  Buffer polyfill (using Uint8Array)
node:crypto     →  Web Crypto API wrapper
node:events     →  EventEmitter polyfill
node:fs         →  No-op or in-memory filesystem
node:http       →  Fetch-based HTTP polyfill
node:path       →  Pure JS path implementation
node:process    →  Minimal process shim
node:stream     →  Web Streams adapter
node:url        →  URL/URLSearchParams polyfill
node:util       →  Utility polyfills

When a Node.js API has no edge equivalent, unenv provides
either a no-op stub or throws a helpful error.

How Nitro uses unenv

// nitro.config.ts — unenv is built into Nitro:
export default defineNitroConfig({
  // Nitro automatically applies unenv polyfills per deploy target:

  preset: "cloudflare-pages",
  // → unenv polyfills node:crypto, node:buffer, node:events
  // → Replaces node:fs with no-op (no filesystem on edge)

  preset: "vercel-edge",
  // → Similar polyfills for Vercel Edge Runtime

  preset: "node-server",
  // → No polyfills needed (native Node.js)
})

// Your server code just uses Node.js APIs:
import { createHash } from "node:crypto"
import { Buffer } from "node:buffer"

export default defineEventHandler(() => {
  const hash = createHash("sha256").update("hello").digest("hex")
  const buf = Buffer.from("hello", "utf-8")
  return { hash, buf: buf.toString("base64") }
})
// Works on Node.js, Cloudflare Workers, Vercel Edge, Deno Deploy

Programmatic usage

import { env } from "unenv"

// Get environment config for a target:
const cloudflareEnv = env("cloudflare")
const vercelEdgeEnv = env("vercel-edge")

// Each env provides:
console.log(cloudflareEnv.alias)
// → {
//   "node:crypto": "unenv/runtime/node/crypto",
//   "node:buffer": "unenv/runtime/node/buffer",
//   "node:events": "unenv/runtime/node/events",
//   ...
// }

console.log(cloudflareEnv.inject)
// → {
//   process: "unenv/runtime/node/process",
//   Buffer: ["unenv/runtime/node/buffer", "Buffer"],
//   ...
// }

// Use with bundlers (Rollup, Webpack, Vite):
// These aliases redirect imports to polyfills at build time

Individual polyfills

// Use individual polyfills in your own code:
import { Buffer } from "unenv/runtime/node/buffer"
import { EventEmitter } from "unenv/runtime/node/events"
import { createHash } from "unenv/runtime/node/crypto"

// These work in browsers, Deno, Cloudflare Workers, etc.
const emitter = new EventEmitter()
emitter.on("data", (msg) => console.log(msg))
emitter.emit("data", "Hello from edge!")

const hash = createHash("sha256").update("test").digest("hex")

edge-runtime

edge-runtime — Vercel edge emulator:

Local development

import { EdgeRuntime } from "edge-runtime"

// Create a local edge runtime:
const runtime = new EdgeRuntime()

// Evaluate code in edge context:
const result = await runtime.evaluate(`
  const response = new Response("Hello from edge!")
  response.text()
`)

console.log(result) // → "Hello from edge!"

Testing edge functions

import { EdgeRuntime, runServer } from "edge-runtime"

// Create runtime with your edge function:
const runtime = new EdgeRuntime({
  initialCode: `
    addEventListener("fetch", (event) => {
      event.respondWith(
        new Response(JSON.stringify({ message: "Hello!" }), {
          headers: { "content-type": "application/json" },
        })
      )
    })
  `,
})

// Run as local HTTP server:
const server = await runServer({ runtime, port: 3000 })

// Test with fetch:
const response = await fetch("http://localhost:3000")
const data = await response.json()
console.log(data) // → { message: "Hello!" }

await server.close()

Available APIs

edge-runtime provides WinterCG-compatible APIs:

Web APIs:
  ✅ fetch, Request, Response, Headers
  ✅ URL, URLSearchParams, URLPattern
  ✅ TextEncoder, TextDecoder
  ✅ ReadableStream, WritableStream, TransformStream
  ✅ AbortController, AbortSignal
  ✅ structuredClone
  ✅ crypto (Web Crypto API)
  ✅ atob, btoa
  ✅ setTimeout, setInterval
  ✅ console

NOT available (by design):
  ❌ node:fs (no filesystem)
  ❌ node:net (no TCP sockets)
  ❌ node:child_process (no subprocesses)
  ❌ eval(), new Function() (no dynamic code)
  ❌ __dirname, __filename (no filesystem)

With Jest/Vitest

// vitest.config.ts — test edge functions locally:
import { defineConfig } from "vitest/config"

export default defineConfig({
  test: {
    environment: "edge-runtime",
  },
})

// __tests__/api.test.ts
describe("Edge API", () => {
  it("returns JSON response", async () => {
    const request = new Request("https://example.com/api")
    const response = await handleRequest(request)
    const data = await response.json()

    expect(response.status).toBe(200)
    expect(data.message).toBe("Hello!")
  })
})

@cloudflare/workers-types

@cloudflare/workers-types — TypeScript types:

Setup

// tsconfig.json
{
  "compilerOptions": {
    "types": ["@cloudflare/workers-types"]
  }
}

// Or with compatibility flags:
// tsconfig.json
{
  "compilerOptions": {
    "types": ["@cloudflare/workers-types/2024-01-01"]
  }
}

Workers handler types

// src/index.ts
export interface Env {
  MY_KV: KVNamespace
  MY_R2: R2Bucket
  MY_DB: D1Database
  MY_DO: DurableObjectNamespace
  API_KEY: string  // Secret
}

export default {
  async fetch(
    request: Request,
    env: Env,
    ctx: ExecutionContext
  ): Promise<Response> {
    const url = new URL(request.url)

    if (url.pathname === "/api/data") {
      // KV:
      const cached = await env.MY_KV.get("key")
      if (cached) return new Response(cached)

      // D1:
      const { results } = await env.MY_DB
        .prepare("SELECT * FROM packages LIMIT 10")
        .all()

      const json = JSON.stringify(results)
      await env.MY_KV.put("key", json, { expirationTtl: 3600 })

      return new Response(json, {
        headers: { "content-type": "application/json" },
      })
    }

    return new Response("Not found", { status: 404 })
  },
}

KV types

// KVNamespace provides typed methods:
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Get:
    const value = await env.MY_KV.get("key")               // string | null
    const json = await env.MY_KV.get("key", "json")         // any | null
    const buffer = await env.MY_KV.get("key", "arrayBuffer") // ArrayBuffer | null
    const stream = await env.MY_KV.get("key", "stream")     // ReadableStream | null

    // Get with metadata:
    const { value: val, metadata } = await env.MY_KV.getWithMetadata<{
      createdAt: string
    }>("key")

    // Put:
    await env.MY_KV.put("key", "value")
    await env.MY_KV.put("key", JSON.stringify(data), {
      expirationTtl: 3600,
      metadata: { createdAt: new Date().toISOString() },
    })

    // List:
    const list = await env.MY_KV.list({ prefix: "user:" })
    // → { keys: [{ name: "user:1", ... }], list_complete: boolean }

    // Delete:
    await env.MY_KV.delete("key")

    return new Response("OK")
  },
}

R2 and D1 types

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // R2 (Object Storage):
    const object = await env.MY_R2.get("images/photo.jpg")
    if (object) {
      return new Response(object.body, {
        headers: {
          "content-type": object.httpMetadata?.contentType ?? "application/octet-stream",
          "etag": object.httpEtag,
        },
      })
    }

    await env.MY_R2.put("images/upload.jpg", request.body, {
      httpMetadata: { contentType: "image/jpeg" },
    })

    // D1 (SQL Database):
    const { results } = await env.MY_DB
      .prepare("SELECT * FROM packages WHERE name = ?")
      .bind("react")
      .all<{ name: string; downloads: number }>()
    // → results: Array<{ name: string; downloads: number }>

    const row = await env.MY_DB
      .prepare("SELECT count(*) as total FROM packages")
      .first<{ total: number }>()
    // → row: { total: number } | null

    return Response.json(results)
  },
}

Feature Comparison

Featureunenvedge-runtime@cloudflare/workers-types
PurposeNode.js polyfillsEdge emulatorTypeScript types
Runtime supportAny (build-time)Vercel EdgeCloudflare Workers
Polyfills✅ (30+ modules)N/AN/A
Local testingN/A❌ (use wrangler)
TypeScript types
KV/R2/D1 types
WinterCG compatible
Used byNitro, NuxtVercel, Next.jsCloudflare Workers
Weekly downloads~5M~1M~500K

When to Use Each

Use unenv if:

  • Building code that runs on Node.js, edge, and browsers
  • Using Nitro or Nuxt for cross-platform deployment
  • Need Node.js API polyfills for edge runtimes
  • Building a framework that targets multiple environments

Use edge-runtime if:

  • Testing Vercel Edge Functions locally
  • Need a WinterCG-compatible test environment
  • Building Next.js middleware or edge routes
  • Want to validate code runs in edge constraints

Use @cloudflare/workers-types if:

  • Building Cloudflare Workers in TypeScript
  • Need types for KV, R2, D1, Durable Objects
  • Want autocomplete for Workers runtime APIs
  • Using wrangler for local development

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on unenv v1.x, edge-runtime v3.x, and @cloudflare/workers-types v4.x.

Compare edge tooling and serverless utilities on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.