Skip to content

RESOURCES / BLOG

What is the JavaScript runtime environment?

If you have ever copied a snippet from a README and watched it work in one app but crash in another, you have already bumped into the idea of a JavaScript runtime environment. Different places where JS runs come with different capabilities, APIs, and limits.

What is the JavaScript runtime environment? I see people mention Node, browser, Deno, Bun, and edge runtimes, and sometimes the same code behaves differently. 

What exactly is a JS runtime, what components does it include, how do these runtimes differ, and how can I write code that works across them or at least detect the environment correctly?

A JavaScript runtime environment is the host that executes your JS and provides the APIs and system bindings your code can use. It bundles a JS engine plus platform features that are not part of the ECMAScript language itself. The most popular JavaScript runtime environments are:

  • Browser: Secure sandbox, no filesystem, rich UI APIs, great for UI and edge streaming via fetch.
  • Node.js: Server-side I/O, broad ecosystem, ESM and CJS, good for servers, CLIs, and workers.
  • Deno: Secure by default with permission flags, built-in TypeScript and URL imports.
  • Bun: Focus on speed and tooling consolidation, strong Node compatibility for many projects.
  • JavaScript engine: Executes JS bytecode or JITs it at runtime (V8, SpiderMonkey, JavaScriptCore).
  • Host APIs: The capabilities exposed to your code. Common ones include:
  • Browser: DOM, fetch, URL, LocalStorage, Web Workers, Canvas.
  • Node.js: fs, net, http, Buffer, process, worker_threads.
  • Deno: Web-standard fetch, URL, crypto plus permissions and stable APIs.
  • Bun: Node-compatible APIs, fast bundler, test runner.
  • Event loop and task queues: Schedule macrotasks and microtasks for async work.
  • Module loader and packaging: ESM, CommonJS, URL imports, TypeScript support, permissions model.

Prefer capability checks over brand checks when possible. If you must branch, keep it minimal.

// Detect runtime and fall back by feature
const runtime = (() => {
  if (typeof Deno !== "undefined") return "deno";
  if (typeof Bun !== "undefined") return "bun";
  if (typeof window !== "undefined" && typeof document !== "undefined") return "browser";
  if (typeof process !== "undefined" && process.versions && process.versions.node) return "node";
  return "unknown";
})();

const httpGetText = async (url) => {
  if (typeof fetch !== "undefined") {
    const res = await fetch(url);
    return res.text();
  }
  // Node environments that predate global fetch
  const { default: fetch } = await import("node-fetch");
  const res = await fetch(url);
  return res.text();
};

(async () => {
  console.log("Runtime:", runtime);
  console.log(await httpGetText("https://example.com"));
})();Code language: JavaScript (javascript)
  • Do not block the event loop with heavy CPU work. Offload to worker threads, child processes, or a queue.
  • Prefer streaming APIs for large responses and file transfers.
  • Use ESM and tree-shaking friendly code to reduce bundle size in browsers and edge.

Media processing is a common place where runtime differences bite. Instead of shipping large native dependencies per runtime, consider pushing heavy lifting to a media platform and using lightweight fetch or simple URLs across environments.

  • Architecture: Host originals centrally and deliver derivatives via URLs. This fits browsers, Node servers, and edge workers with the same code path. See concepts in understanding image hosting for websites.
  • Automatic formats: Deliver images with f_auto,q_auto so each client gets an optimal format like WebP or AVIF. Background on formats in WebP format technology.
  • Video workflows: Offload expensive transcodes. If you currently script FFmpeg inside Node, compare options in FFmpeg features and use cases and consider URLs that request ready-to-stream renditions.
  • Try utilities without code using Cloudinary tools to explore transformations before integrating.

Example: Server-side upload in Node.js, client delivery everywhere.

// Node.js server example
import { v2 as cloudinary } from "cloudinary";

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET
});

// Upload once
const result = await cloudinary.uploader.upload("./images/hero.jpg", {
  folder: "assets"
});

// Deliver optimized everywhere with a URL
// Works in browsers, Node renderers, and edge handlers
const optimizedUrl = `https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/upload/f_auto,q_auto,w_1200,c_fill/${result.public_id}.jpg`;
console.log(optimizedUrl);Code language: JavaScript (javascript)

For edge runtimes, skip SDKs that rely on Node-only modules and use the same delivery URL pattern. You still get dynamic resizing, format negotiation, and caching at the CDN edge.

  • Assuming Node globals in edge or browser. Fix: feature-detect and branch only where required.
  • Using synchronous I/O in Node. Fix: prefer async APIs to keep the event loop free.
  • Hardcoding CommonJS in ESM-only contexts. Fix: use ESM or dual exports and dynamic import().
  • A JS runtime is an engine plus host APIs, event loop, and module loader.
  • Browsers focus on Web APIs and security, Node on server I/O, Deno on security and standards, Bun on speed, Edge on lightweight execution.
  • Feature-detect, stream data, and avoid blocking the event loop.
  • For media, push heavy processing to a service and use optimized URLs so your code runs consistently across runtimes.

Ready to streamline media across any JavaScript runtime? Create your free Cloudinary account and start optimizing images and video with simple URLs that work everywhere.

Start Using Cloudinary

Sign up for our free plan and start creating stunning visual experiences in minutes.

Sign Up for Free