Why Thousands of Developers Are Mass Migrating to PushForge for Web Push Notifications
_"crypto.createECDH is not a function" - If you've seen this error, you're not alone. Here's why the web push landscape is finally changing._
If you've ever tried implementing push notifications on Cloudflare Workers, Vercel Edge, or any modern edge runtime, you've probably hit the same wall that thousands of developers face every month:
TypeError: crypto.createECDH is not a function
Or maybe this one:
ReferenceError: https.request is not available in edge runtime
These errors aren't bugs in your code. They're symptoms of a fundamental problem: **the most popular web push library was built for a world that no longer exists**.
## The Uncomfortable Truth About web-push
For years, `web-push` has been the de facto standard for sending push notifications in Node.js. With 18+ million monthly downloads, it's the default choice that tutorials recommend, Stack Overflow answers reference, and ChatGPT suggests.
But here's what those recommendations don't tell you:
The Promise | The Reality
---|---
"Works everywhere" | Fails on Cloudflare Workers (#718)
"Simple setup" | Requires 5+ nested dependencies
"Production ready" | Node.js deprecation warnings in v24
"Modern JavaScript" | Uses legacy `crypto.createECDH` API
**The issue has been open since 2022.** The workarounds are fragile. The compatibility flags are band-aids. And the edge computing revolution isn't waiting.
## Enter PushForge: Built for Where You're Deploying Today
**PushForge** is a zero-dependency web push library built entirely on the Web Crypto API, the same standard that runs in every modern browser, every edge runtime, and every JavaScript environment.
import { buildPushHTTPRequest } from "@pushforge/builder";
const { endpoint, headers, body } = await buildPushHTTPRequest({
privateJWK: VAPID_PRIVATE_KEY,
subscription: userSubscription,
message: {
payload: { title: "Hello!", body: "This works everywhere." },
adminContact: "mailto:admin@example.com"
}
});
await fetch(endpoint, { method: "POST", headers, body });
That's it. No polyfills. No compatibility flags. No "it works on my machine."
## The Comparison Developers Are Making
Feature | PushForge | web-push
---|---|---
Dependencies | **0** | 5+ (with transitive deps)
Cloudflare Workers | **Yes** | No (#718)
Vercel Edge Functions | **Yes** | No
Convex | **Yes** * | No
Deno | **Yes** | Limited
Bun | **Yes** | Partial
TypeScript | **Native** | @types package
Bundle Size | **~15KB** | ~150KB+ with deps
_* Convex requires`"use node";` directive_
## The Numbers: Organic Growth That Speaks Volumes
**72,500+ monthly downloads** and growing. Zero paid marketing. Just developers solving real problems and telling others.
PushForge launched in April 2025 and has seen consistent month-over-month growth as edge computing adoption accelerates. The trajectory tells you which direction the industry is moving.
## Why PushForge Works Where web-push Fails
The difference comes down to one architectural decision: **standards vs. legacy APIs**.
### web-push approach (Node.js specific):
const crypto = require('crypto');
const https = require('https');
const ecdh = crypto.createECDH('prime256v1'); // Node.js only
### PushForge approach (Web Standards):
const keyPair = await crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
true, ['deriveBits']
); // Works everywhere
The Web Crypto API is:
* Available in every modern browser
* Supported by Cloudflare Workers, Vercel Edge, Deno, Bun
* Part of Node.js 20+ without flags
* The W3C standard for cryptographic operations
**PushForge doesn't "polyfill" or "shim" its way to compatibility. It's built on the foundation that every JavaScript runtime shares.**
## Try Before You Trust: The Interactive Playground
Don't take our word for it. **Test PushForge in your browser right now:**
**pushforge.draphy.org**
The playground lets you:
* **Quick Test** : Enable notifications, send a test message, see it arrive
* **Topic Channels** : Subscribe to named topics, target specific groups
* **Full Customization** : Icons, images, action buttons, vibration patterns
* **Push Options** : Test urgency levels (battery hints), TTL, topic replacement
* **Cross-Browser** : Chrome, Firefox, Safari 16+, Edge, Brave
The backend? A single Cloudflare Worker using `buildPushHTTPRequest()`. Zero dependencies. The same code you'd write.
Subscriptions auto-expire (5 minutes for quick test, 1 hour for topics). No accounts. No data stored. Just push notifications, working.
## Getting Started in 60 Seconds
### 1. Install
npm install @pushforge/builder
### 2. Generate VAPID Keys
npx @pushforge/builder vapid
Save the output: public key for your frontend, private key (JWK) for your server.
### 3. Subscribe Users (Frontend)
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: VAPID_PUBLIC_KEY
});
// Send subscription to your server
### 4. Send Notifications (Server)
import { buildPushHTTPRequest } from "@pushforge/builder";
const { endpoint, headers, body } = await buildPushHTTPRequest({
privateJWK: JSON.parse(process.env.VAPID_PRIVATE_KEY),
subscription,
message: {
payload: {
title: "New Message",
body: "You have a notification!",
icon: "/icon.png",
data: { url: "/messages" }
},
adminContact: "mailto:you@example.com",
options: {
ttl: 3600,
urgency: "high"
}
}
});
const response = await fetch(endpoint, { method: "POST", headers, body });
### Platform Examples Ready to Copy
**Cloudflare Workers:**
export default {
async fetch(request, env) {
const { endpoint, headers, body } = await buildPushHTTPRequest({
privateJWK: JSON.parse(env.VAPID_PRIVATE_KEY),
subscription: await request.json(),
message: {
payload: { title: "Hello from the Edge!" },
adminContact: "mailto:admin@example.com"
}
});
return fetch(endpoint, { method: "POST", headers, body });
}
};
**Vercel Edge Functions:**
export const config = { runtime: "edge" };
export default async function handler(request: Request) {
const { endpoint, headers, body } = await buildPushHTTPRequest({
privateJWK: JSON.parse(process.env.VAPID_PRIVATE_KEY!),
subscription: await request.json(),
message: {
payload: { title: "Edge Notification" },
adminContact: "mailto:admin@example.com"
}
});
await fetch(endpoint, { method: "POST", headers, body });
return new Response("Sent");
}
**Deno:**
import { buildPushHTTPRequest } from "npm:@pushforge/builder";
const { endpoint, headers, body } = await buildPushHTTPRequest({
privateJWK: JSON.parse(Deno.env.get("VAPID_PRIVATE_KEY")!),
subscription,
message: {
payload: { title: "Hello from Deno!" },
adminContact: "mailto:admin@example.com"
}
});
await fetch(endpoint, { method: "POST", headers, body });
## What Makes PushForge Different
### TypeScript-First, Not TypeScript-Added
PushForge isn't a JavaScript library with type definitions bolted on. It's written in TypeScript from the ground up:
import type {
BuilderOptions,
PushMessage,
PushSubscription
} from "@pushforge/builder";
Your IDE knows the shape of every parameter. Your compiler catches mistakes before runtime. Your code documents itself.
### Security Built In, Not Bolted On
PushForge validates everything before processing:
* VAPID key structure (EC P-256 curve with required x, y, d parameters)
* Subscription endpoints (must be valid HTTPS URLs)
* p256dh keys (65-byte uncompressed P-256 point format)
* Auth secrets (exactly 16 bytes)
* Payload size (max 4KB per Web Push spec)
* TTL bounds (max 24 hours per VAPID spec)
Invalid input fails fast with clear error messages, not cryptic failures deep in the crypto stack.
## The Roadmap: What's Coming
PushForge is actively developed. Here's what's on the horizon:
* **Batching & Queuing**: Bulk notification delivery with rate limiting
* **Built-in Retry Logic** : Automatic error handling for push service failures
* **Framework Examples** : Ready-to-use templates for React, Vue, Next.js, SvelteKit
* **Service Worker Templates** : Drop-in notification handling code
Follow development: github.com/draphy/pushforge
## The Developer Community
PushForge welcomes contributions. The workflow is straightforward:
1. Open an issue describing your change
2. Fork the repository
3. Create a branch: `username/wpn-issuenumber-description`
4. Follow conventional commit guidelines
5. Submit a pull request
The codebase uses modern tooling:
* **Biome** for fast formatting and linting
* **Vitest** for comprehensive testing
* **Semantic Release** for automated versioning
* **GitHub Actions** for CI/CD
## Why Developers Are Switching
Here's what the migration typically looks like:
**Before (web-push):**
const webpush = require('web-push');
webpush.setVapidDetails(
'mailto:admin@example.com',
publicKey,
privateKey
);
// Hope you're not on Cloudflare Workers...
await webpush.sendNotification(subscription, payload);
**After (PushForge):**
import { buildPushHTTPRequest } from "@pushforge/builder";
const { endpoint, headers, body } = await buildPushHTTPRequest({
privateJWK,
subscription,
message: { payload, adminContact: "mailto:admin@example.com" }
});
// Works on ANY runtime with fetch()
await fetch(endpoint, { method: "POST", headers, body });
The API is intentionally minimal. You get `endpoint`, `headers`, and `body`. You use `fetch()`. That's the entire interface, and it works everywhere `fetch()` works.
## The Bottom Line
The web has evolved. Serverless is the default. Edge computing is mainstream. The tools should match the territory.
**PushForge is:**
* Zero dependencies (no supply chain surprises)
* Web standards based (no Node.js lock-in)
* TypeScript native (no type guessing)
* Edge ready (Cloudflare, Vercel, Deno, Bun - all first-class)
* Battle tested (70,000+ monthly downloads and growing)
* Open source (MIT licensed, transparent development)
**web-push served its era well.** But if you're deploying to modern infrastructure, you deserve a modern foundation.
## Get Started Now
npm install @pushforge/builder
* **Documentation** : github.com/draphy/pushforge
* **npm** : npmjs.com/package/@pushforge/builder
* **Playground** : pushforge.draphy.org
_PushForge is MIT licensed and open source. Created by David Raphi._
_Have questions? Open an issue. Found a bug? PRs welcome. Building something cool? I'd love to hear about it._