Which Runtime Should I Use?
A decision guide for choosing between Node.js, Bun, Deno, edge, and serverless — the fast answer and the criteria behind each choice.
Decision guide
Five runtimes run the same NextRush application code unmodified. The choice isn't "which one works" — it's "which deployment model fits what you're building." Here's the fast answer, then the reasoning behind it.
Already have an app in Next.js (or another framework)?
This page is about choosing a JS runtime for a standalone NextRush app. If you already have — or are creating — a Next.js project and want to mount a NextRush API inside it, that's a different question with its own page: see Next.js integration. Next.js isn't a runtime choice; it runs on whichever runtime it's deployed to.
The fast answer
| Situation | Recommendation |
|---|---|
| ⭐ New to NextRush, or unsure | Node.js |
| 🐳 Deploying with Docker, PM2, or a VPS | Node.js |
| ⚡ Fastest local dev loop | Bun |
| 🔒 Deny-by-default, security-first | Deno |
| 🌍 Cloudflare Workers or Vercel Edge | Edge |
| ☁ AWS Lambda, GCF, or Azure Functions | Serverless |
Which package do I actually install? (Cloudflare vs. Lambda/GCF/Azure)
"Edge" and "serverless" both describe event-driven, no-server-to-manage deployment — but they're two different NextRush packages, split by which handler contract the platform speaks, not by vaguely feeling "serverless." This table is the answer, since the split isn't obvious from either package's name alone:
| You're deploying to... | Install | Handler |
|---|---|---|
| Cloudflare Workers | @nextrush/adapter-edge | createCloudflareHandler(app) |
| Vercel Edge Functions | @nextrush/adapter-edge | createVercelHandler(app) |
| Netlify Edge Functions | @nextrush/adapter-edge | createNetlifyHandler(app) |
| AWS Lambda (Function URL / API Gateway) | @nextrush/adapter-serverless | createLambdaHandler(app) |
| Google Cloud Functions | @nextrush/adapter-serverless | createGoogleHandler(app) (alias: createGcfHandler) |
| Azure Functions | @nextrush/adapter-serverless | createAzureHandler(app) |
The rule of thumb: Cloudflare/Vercel/Netlify speak the Fetch API directly (fetch(request)) —
that's @nextrush/adapter-edge. AWS/GCP/Azure hand you a platform-specific event or SDK object
— that's @nextrush/adapter-serverless, which is itself built on @nextrush/adapter-edge's
engine underneath (one execution model, six front doors). See each package's own README for the
full handler reference: adapter-edge,
adapter-serverless.
Still unsure? Start with Node.js
Every tutorial, every guide, and every middleware package on this site targets Node first — it's the runtime with zero surprises to design around. Moving to Bun, Deno, edge, or serverless later changes one import, not your route handlers.
Changing your mind later is cheap
Switching runtimes changes the adapter import — @nextrush/adapter-bun,
@nextrush/adapter-deno, @nextrush/adapter-edge, or @nextrush/adapter-serverless in place
of the Node adapter nextrush bundles by default. Your route handlers, middleware, and the
Context API (ctx.json, ctx.params, ctx.query, …) stay exactly the same — see each
runtime's own guide for the one import that moves.
By project type
| You're building... | Reach for |
|---|---|
| A REST API or backend service | Node.js |
| An internal dashboard or admin tool | Node.js |
| A Cloudflare Worker | Edge |
| A function behind AWS Lambda / API Gateway | Serverless |
| A CLI tool or script that also serves HTTP | Bun |
| Something handling untrusted or third-party code | Deno |
Each runtime, compressed
🟢 Node.js — recommended default
Best for: REST APIs, Docker, PM2, production services. Avoid if: you only ever deploy to Workers or Lambda and never run a local process.
Bun — fast local loop
Best for: fast startup, native TypeScript with no tsx step. Avoid if: you need Node's
longer production track record for a critical service.
Deno — secure by default
Best for: deny-by-default filesystem/network/env access, enforced by the process itself.
Avoid if: you don't want to manage --allow-* flags on every invocation, including local dev.
Edge — stateless & global
Best for: answering from the network location closest to the caller, no server to patch. Avoid if: your app assumes a long-running process or in-memory state between requests.
Serverless — event-driven
Best for: per-event billing and scaling with no infrastructure to manage. Avoid if: cold starts are unacceptable for your latency budget and you can't pin a warm instance.
A tempting reason that's usually wrong
Picking Bun or Deno "because it's faster/newer" with no concrete requirement behind it trades Node's ecosystem and track record for a benefit you may never measure. Picking edge or serverless for a stateful app that assumes a long-running process is the more expensive version of the same mistake.
Comparison reference
Once you've picked a direction above, this is what actually differs underneath. Every row is identical regardless of which runtime you choose except the ones listed:
| Node.js | Bun | Deno | Edge | Serverless | |
|---|---|---|---|---|---|
| Process model | Long-running | Long-running | Long-running | Per-request fetch | Per-invocation |
node:* module access | Full | Full (Bun's compat layer) | Partial (npm specifiers) | None | None |
| Filesystem | Yes | Yes | Yes, with --allow-read | No | No |
| Default security model | Open | Open | Deny-by-default | Sandboxed by platform | Sandboxed by platform |
| Cold start | None (process stays up) | None (process stays up) | None (process stays up) | Lazy boot, first request | Cold start per fresh instance |
Extension teardown (app.close()) | Runs normally | Runs normally | Runs normally | Not guaranteed | Not guaranteed |
| Adapter package | Bundled in nextrush | @nextrush/adapter-bun | @nextrush/adapter-deno | @nextrush/adapter-edge | @nextrush/adapter-serverless |
| Verified in CI | 🟢 Real runtime | 🟢 Real runtime | 🟢 Real runtime | 🟢 Cloudflare · 🟡 Vercel | 🟡 Simulated |
"🟢 Real runtime" vs. "🟡 Simulated" reflects how each adapter's conformance suite is verified —
see the compatibility matrix for the full breakdown.
createApp, createRouter, the Context API, and route handlers behave identically across every
row above — nothing in this table changes how you write a handler.
No fabricated performance scores
You won't find star ratings for "startup speed" or "throughput" on this page. The framework's
own benchmark numbers are being re-measured
on a controlled environment and aren't currently republished — a 1–5 star score with no
measurement behind it would be a guess dressed up as data. Run apps/benchmark
yourself for numbers specific to your hardware and workload.
Common mistakes
- ⚠ Edge isn't for stateful apps — no instance guarantee between requests; shared state needs an external store, on every runtime.
- ⚠ Serverless needs module-scope initialization — build the app outside the handler function or warm-instance reuse never kicks in (serverless guide, Step 3).
- ⚠ Deno permissions are enforced by Deno, not NextRush —
--allow-netand friends gate the process itself, before any adapter code runs.