When resizing 100 images in 4K in the browser, standard JS on Canvas yields 2 FPS — the interface freezes. After replacing with a WebAssembly (WASM) module in Rust, we get stable 60 FPS without blocking the rendering thread. On a photo editor project, we achieved a 10x speedup, allowing the client to save up to 30% on cloud computing and reduce server costs by up to 40%. In another case with a CAD engine, replacing calculations with WASM cut drawing generation time from 12 to 0.8 seconds.
WASM is a binary instruction format for the browser's virtual machine. It takes over code where native speed is critical: codecs, cryptography, image processing, physics engines, CAD, ML inference. WASM runs in an isolated sandbox and is called from JS like a regular function. Support exists in all modern browsers — details at WebAssembly | MDN.
Performance comparison: JS vs WASM on 4K JPEG resize
| Method | Resize time | FPS | Binary size |
|---|---|---|---|
| Canvas 2D | 450 ms | 2.2 | 0 KB (browser native) |
| WebAssembly (Rust) | 45 ms | 22 | 280 KB compressed |
| WebAssembly + Worker | 48 ms | 20 (UI not blocked) | 295 KB |
WASM version is 10x faster for a single operation and allows the main rendering thread to breathe.
Why choose Rust for compiling to WASM?
Rust is the leader in Developer Experience for WASM. The wasm-pack tool generates bindings automatically, and wasm-bindgen supports complex types (strings, arrays) without manual memory management. We use Rust in 80% of WASM projects. Example image resize code:
// src/lib.rs — example image resize use wasm_bindgen::prelude::*; use image::{DynamicImage, ImageFormat}; use std::io::Cursor; #[wasm_bindgen] pub fn resize_image(data: &[u8], width: u32, height: u32) -> Vec<u8> { let img = image::load_from_memory(data).unwrap(); let resized = img.resize_exact(width, height, image::imageops::FilterType::Lanczos3); let mut output = Cursor::new(Vec::new()); resized.write_to(&mut output, ImageFormat::WebP).unwrap(); output.into_inner() } Command wasm-pack build --target web --release produces a ready-to-integrate module.
How to load WASM without blocking the interface?
Heavy computations should be offloaded to a Web Worker. Here's a minimal TypeScript implementation:
// wasm-worker.ts import init, { resize_image } from './pkg/image_processor'; let initialized = false; self.onmessage = async (event: MessageEvent) => { const { id, type, payload } = event.data; if (!initialized) { await init(); initialized = true; } if (type === 'RESIZE') { const { imageData, width, height } = payload; const result = resize_image(new Uint8Array(imageData), width, height); self.postMessage({ id, type: 'RESULT', payload: result.buffer }, [result.buffer]); } }; Passing buffer via Transferable avoids copying — data moves between threads in O(1).
Which tasks are best suited for WASM?
Besides image processing, WASM is effective for:
- Cryptographic algorithms (AES, hashing) — up to 5× speedup.
- Compression and decompression (Zlib, Brotli) — 3–4× time reduction.
- Physics simulations in games and CAD — stable 60 FPS.
- ML inference on the client — running models directly in the browser without sending data to the server.
Comparison of approaches: Rust vs C++ for WASM
| Criterion | Rust (wasm-pack) | C++ (Emscripten) |
|---|---|---|
| Memory management | Automatic (no GC) | Manual (new/delete) |
| Binding generation | wasm-bindgen | Embind |
| Binary size | ~200 KB (minimal) | ~400 KB (with runtime) |
| Compilation speed | Fast (LLVM) | Moderate |
Rust is preferable for new projects, C++ for porting legacy code.
What is included in the work on WASM integration?
- Analysis of JS bottlenecks: Core Web Vitals, execution time, data volume.
- Choice of target language (Rust, C/C++) or ready WASM package.
- Compilation and binding generation (wasm-pack / Emscripten).
- Integration via Web Worker with Transferable objects.
- Binary size optimization: tree-shaking, LTO, caching configuration.
- Documentation on build and deployment, repository access.
Process: from analysis to deployment
- Analytics — study current code, measure performance, identify WASM candidates.
- Design — choose stack and module architecture (Worker + Transferable).
- Implementation — write code in Rust/C, compile, test.
- Integration — connect module in project, configure HTTP headers for SharedArrayBuffer if needed.
- Optimization and deploy — reduce binary size, check Core Web Vitals, push to production.
Timeline: from 3 to 5 days. Cost is calculated individually, but on average the project pays off in 2–3 months.
Typical mistakes when working with WASM
- Forgetting to set headers
Cross-Origin-Embedder-Policy: require-corpandCross-Origin-Opener-Policy: same-originforSharedArrayBuffer. - Calling WASM functions in the main thread — blocks UI. Needs Worker.
- Passing data via copying instead of Transferable — loses speed gain.
Our experience: 10+ years in web development, 50+ projects with WASM. We guarantee optimization of Core Web Vitals and at least 2× speedup. Get a consultation on your project — write to us. Also order a performance audit of your application.







