Back to Blog

WebAssembly in Production: When It Is Actually the Right Tool

WebAssembly in Production: When It Is Actually the Right Tool cover image

A client had a browser tool that let users trim and compress video before uploading. It worked by uploading the raw file to a server, processing it with ffmpeg, and sending the result back. A 400MB phone video meant a long upload on a bad connection, a queue, server CPU, and egress charges — for a file the user was about to shrink to 30MB anyway.

Moving that processing into the browser with WebAssembly removed the upload entirely. The user's own laptop did the work, and we uploaded the 30MB result. The server bill dropped, the experience got faster, and the feature started working offline.

That is WebAssembly's actual pitch, and it is narrower and more useful than the "run any language on the web" framing suggests.

What It Actually Is

Wasm is a compact binary instruction format that browsers execute in a sandbox at near-native speed. Languages like Rust, C, C++ and Go compile to it. It runs in the same process as your JavaScript, in the same security sandbox, with no plugin and no install.

Three properties define what it is good for.

It is fast at computation. Predictable performance, no garbage collector pauses, no JIT warm-up. For tight numeric loops it is several times faster than JavaScript, sometimes an order of magnitude.

It has no access to anything by default. No DOM, no filesystem, no network. It gets a block of linear memory and whatever functions you explicitly hand it. That is a genuine security property, not a limitation to work around.

Crossing the boundary costs something. Calling a Wasm function from JavaScript is cheap; passing large or complex data across is not, because anything beyond numbers has to be copied into or read out of Wasm's memory.

That last point decides almost every design question. Wasm wins when you hand it a big job and get one answer back. It loses when JavaScript and Wasm chat constantly.

// Bad: 100k boundary crossings, each with marshalling overhead.
for (const px of pixels) out.push(wasm.adjust(px, gamma));

// Good: one crossing. Wasm works directly in shared linear memory.
const ptr = wasm.alloc(pixels.length);
new Uint8Array(wasm.memory.buffer, ptr, pixels.length).set(pixels);
wasm.adjust_all(ptr, pixels.length, gamma);
const out = new Uint8Array(wasm.memory.buffer, ptr, pixels.length).slice();
wasm.free(ptr, pixels.length);

Where It Is Genuinely the Right Call

Media processing in the browser. Video trimming, image manipulation, audio encoding. ffmpeg compiled to Wasm is a real, widely used thing, and it turns a server-side pipeline into a client-side feature.

Reusing an existing engine. This is the underrated one. If your company has a C++ pricing engine, a physics simulation, or a document parser that has been correct for fifteen years, Wasm lets you run that exact code in a browser instead of reimplementing it in JavaScript and maintaining two versions that disagree at the edges.

Heavy interactive tools. CAD, design software, photo editors, IDEs, games. The applications that were desktop-only because the browser could not keep up.

Privacy-driven local processing. Anything the user would rather not upload — medical images, financial documents, personal video. "It never left your device" is an architecture, not a promise.

On-device inference. Small models running client-side, which is increasingly practical and shares the same motivation.

Where It Is the Wrong Tool

Anything DOM-heavy. Wasm cannot touch the DOM directly; every interaction goes through JavaScript. A UI framework compiled to Wasm pays boundary costs on every update to do a job JavaScript already does well. This is why "rewrite your React app in Wasm" has not happened, and should not.

Small or infrequent work. The module has to download, compile and instantiate. If the computation takes 3ms, you have spent more on setup than you saved.

I/O-bound work. Waiting on the network is exactly as slow in either language.

Anywhere bundle size dominates. A Wasm module is often hundreds of kilobytes to several megabytes. Worth it for a video editor; not worth it for form validation.

The Practical Details Nobody Mentions

Load it lazily and off the main thread. Instantiate inside a Web Worker so a long computation does not freeze the page, and only download the module when the user actually opens the feature that needs it.

Use streaming instantiation. WebAssembly.instantiateStreaming(fetch(url)) compiles while downloading rather than after. It is one line and it is meaningfully faster.

Memory does not shrink. Wasm linear memory grows and stays grown for the life of the instance. A tool that processes a 2GB file will hold that memory afterwards. If users process several large files in a session, tear the instance down and create a fresh one between jobs.

Debugging is worse. Source maps exist and are imperfect. Assume you will debug the original code in its native toolchain and treat the Wasm build as an artefact.

Threads need specific headers. Shared memory and threads require cross-origin isolation headers, which can break third-party embeds on the same page. Find that out before you architect around threading.

Wasm Outside the Browser

The part that has grown fastest lately, and the part I am watching more closely than the browser story.

Because a Wasm module is small, starts in microseconds, and cannot reach anything it was not handed, it is a strong fit for edge compute and plugin systems. Edge platforms use it to run customer code close to users without container startup cost. Products use it to let customers write extensions in any language and run them safely inside the host, which is genuinely difficult to do any other way.

WASI, the standard interface giving Wasm controlled access to files, clocks and sockets, is what makes this practical outside a browser. It is maturing but still moving, so I would build on it with the expectation of change.

Should You Learn It?

Not speculatively. Wasm is a tool for a specific shape of problem, and if you do not have that shape, learning it now will not help you.

The shape is: a computationally expensive job, on data that is already on the user's machine, that you are currently sending to a server. If you can name one of those in your product, Wasm is likely the best answer available, and you can get a working version in days using an existing compiled library rather than writing any Rust yourself.

If you cannot name one, keep it in the back of your mind. That video upload feature ran the slow way for two years before anyone questioned why the server was doing work the user's laptop could do for free.

Related Posts