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.
- PNG to WebP Converter
- Image Upscaling and Quality Enhancement
- MOV to MP4 Converter
- Video as a Service Guide
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.