---
title: How to Deploy Rust WASM Applications
summary: Deploy Rust WASM apps by adding the wasm32-unknown-unknown target, building in release mode, and serving the output file to the browser.
lang: en
slug: how-to-deploy-rust-wasm-applications
published: 2026-04-17T12:05:42+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-deploy-rust-wasm-applications/
---

# How to Deploy Rust WASM Applications



## The engine and the car

You spent weeks optimizing a pathfinding algorithm in Rust. It runs in milliseconds. Now you want to drop it into a web app so your users can see the magic. You compile, grab the `.wasm` file, and drop it into an HTML script tag. The browser loads the file, stares at it, and throws a cryptic error about "indirect calls" or "memory not found." You have the binary, but the browser doesn't know how to talk to it.

Rust and JavaScript live in different worlds. Rust manages its own memory and expects a specific calling convention. JavaScript uses garbage collection and has its own type system. WebAssembly bridges the gap, but it doesn't speak JavaScript directly. You need a transmission that connects the two.

## The engine and the car

Think of WebAssembly as a high-performance engine block. It is incredibly efficient at doing math and processing data. But it has no steering wheel, no radio, and no connection to the dashboard. JavaScript is the rest of the car. It handles the UI, the network requests, and the user input.

To make the engine work, you need a transmission. In Rust, that transmission is `wasm-bindgen`. It generates the glue code that lets JavaScript call your Rust functions and pass data back and forth without manual memory management. `wasm-bindgen` converts Rust types like `String` and `Vec` into JavaScript types like `string` and `Array`. It allocates a shared memory buffer so both sides can read and write data safely.

The community standard for orchestrating this process is `wasm-pack`. It wraps `wasm-bindgen`, runs the Rust compiler, optimizes the binary size, and generates the JavaScript wrapper. You rarely need to call `wasm-bindgen` directly. `wasm-pack` is the convention because it handles the toolchain configuration and optimization steps that are easy to get wrong.

## The target triple: wasm32-unknown-unknown

Rust uses target triples to describe the platform you are compiling for. The format is `architecture-vendor-os`. For WebAssembly in the browser, the target is `wasm32-unknown-unknown`.

The `wasm32` part specifies the WebAssembly architecture with 32-bit pointers. The first `unknown` means there is no specific vendor. The second `unknown` means there is no operating system. This target produces a raw WebAssembly binary that expects the JavaScript engine to provide the runtime environment. It has no access to the file system, no standard library I/O, and no threading support.

This is the correct target for browser deployment. If you see `wasm32-wasip1`, that is a different target for server-side runtimes like Wasmtime or Wasmer. The `wasip1` target includes support for system calls, file access, and standard I/O. Using the wrong target breaks the build or produces a binary that the runtime cannot load.

Install the target with rustup:

```bash
rustup target add wasm32-unknown-unknown
```

## Building with wasm-pack

Start with a library crate. Binary crates with `main` functions do not work well for WebAssembly because the browser needs to import functions, not run a program. Create a new library and add `wasm-bindgen` as a dependency.

```toml
[package]
name = "wasm-demo"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"
```

The `crate-type = ["cdylib"]` line is essential. It tells Rust to compile the library as a dynamic library, which is the format required for WebAssembly. Without this, `cargo build` produces a static archive that cannot be loaded by the browser.

Write a function and mark it for export:

```rust
use wasm_bindgen::prelude::*;

/// This attribute marks the function for export.
/// Without it, wasm-bindgen ignores the function,
/// and JavaScript cannot see it.
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
```

Build the project with `wasm-pack`:

```bash
wasm-pack build --target web
```

The `--target web` flag tells `wasm-pack` to generate output suitable for loading directly in a browser. It creates a `pkg` directory containing the `.wasm` file, a `.js` wrapper, and TypeScript definitions. The `.js` file is the entry point. It handles loading the `.wasm` binary and setting up the memory.

Do not import the `.wasm` file directly in your frontend. Import the `.js` file. The JavaScript wrapper manages the module instantiation and exports the Rust functions as JavaScript functions.

## Loading the module

In your HTML, load the generated JavaScript file. Use `instantiateStreaming` for the best performance. It parses the WebAssembly bytes while they are still downloading, reducing startup time.

```html
<!DOCTYPE html>
<html>
<head>
    <title>Rust WASM Demo</title>
</head>
<body>
    <script type="module">
        // instantiateStreaming fetches and compiles the module in one network round-trip.
        // This is faster than fetching the bytes and then compiling them separately.
        import init from './pkg/wasm_demo.js';

        init().then(instance => {
            // The instance.exports object contains the exported functions.
            const result = instance.exports.add(10, 20);
            console.log('Result:', result);
        });
    </script>
</body>
</html>
```

The `init` function returned by the generated JavaScript file handles all the setup. It loads the `.wasm` file, allocates memory, and returns a promise that resolves to the module instance. You call the exported functions directly from the instance.

## Talking to the browser

Rust code in WebAssembly cannot access the DOM or browser APIs directly. You need bindings. The `web-sys` crate provides safe Rust wrappers for the Web API. It is generated from the Web IDL specifications and keeps type safety.

Add `web-sys` to your dependencies:

```toml
[dependencies]
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["console", "Window", "Document", "Element"] }
```

The `features` list is required. `web-sys` uses feature flags to control code size. Only the features you enable are compiled into your binary. This keeps the size small.

Use `web-sys` to interact with the console or DOM:

```rust
use wasm_bindgen::prelude::*;
use web_sys::console;

/// Log a message to the browser console.
/// web_sys provides type-safe access to browser APIs.
#[wasm_bindgen]
pub fn log_message(msg: &str) {
    // JsValue is the bridge type between Rust and JS.
    // It handles the conversion automatically.
    console::log_1(&JsValue::from_str(msg));
}
```

Convention aside: `web-sys` functions often take `&JsValue` or `Option<&JsValue>`. This reflects the dynamic nature of JavaScript. The compiler ensures you pass the right types, but you still need to handle `None` cases where the API might return null.

## Pitfalls and gotchas

Binary size is the enemy in WebAssembly. Every kilobyte adds latency for your users. The Rust standard library is large. Functions like `println!` pull in the entire I/O stack, which can bloat your binary by hundreds of kilobytes. Avoid `std::io` and `std::fs` in browser targets. They do not work and they increase size.

Use `web_sys::console::log` for debugging. It is lightweight and integrates with the browser developer tools. For production logging, use a crate like `tracing-wasm` that sends logs to the console or a remote endpoint.

If you try to return a custom struct from a function without marking it, the compiler rejects you with `E0277` (trait bound not satisfied). The struct needs `#[wasm_bindgen]` to generate the conversion logic. `wasm-bindgen` only knows how to handle types that implement `IntoWasmAbi` and `FromWasmAbi`. Primitive types and `String` work out of the box. Custom types need the attribute.

```rust
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct Point {
    x: i32,
    y: i32,
}

#[wasm_bindgen]
impl Point {
    #[wasm_bindgen(constructor)]
    pub fn new(x: i32, y: i32) -> Point {
        Point { x, y }
    }

    pub fn distance(&self, other: &Point) -> f64 {
        let dx = self.x - other.x;
        let dy = self.y - other.y;
        (dx * dx + dy * dy) as f64
    }
}
```

The `#[wasm_bindgen(constructor)]` attribute marks a function as the constructor. JavaScript can then call `new Point(10, 20)`. Without it, the function is just a static method.

Memory access errors are common when you return references that escape the function boundary. WebAssembly has a linear memory model. Rust references are pointers into that memory. If you return a reference to a local variable, the memory is freed when the function returns. JavaScript then accesses invalid memory. `wasm-bindgen` prevents many of these errors at compile time, but complex interactions can still cause issues.

Treat binary size as a first-class metric. Every kilobyte adds latency for your users.

## Decision: choosing your toolchain

Use `wasm-pack` for browser applications when you need a complete build pipeline that compiles Rust, generates JavaScript glue code, and optimizes the binary size in one command.

Use `wasm-pack --target bundler` when your frontend project uses Webpack, Vite, or Rollup and requires ES module exports that integrate with your asset pipeline.

Use `wasm-pack --target web` when you are loading the WebAssembly module directly in a browser via a script tag without a bundler.

Use `wasm-bindgen` manually when you are constructing a custom build tool or need precise control over the generated JavaScript bindings.

Use `wasm32-unknown-unknown` when deploying to the browser where the JavaScript engine provides the runtime environment.

Use `wasm32-wasip1` when deploying to server-side runtimes like Wasmtime or Wasmer that support system calls and file access.

Match the target to the runtime. The browser expects `unknown-unknown`. The server expects `wasip1`. Mixing them breaks the build.

## Where to go next

- [How to Build a Frontend Application with Leptos](/en/how-to-build-a-frontend-application-with-leptos/)
- [How to Use Rust WASM for Serverless Functions (Cloudflare Workers, Fastly)](/en/how-to-use-rust-wasm-for-serverless-functions-cloudflare-workers-fastly/)