← Back to Lessons Lesson 16 of 28
Advanced getting-started

Deploying to Production

Build for Production

# Release build with size optimization
wasm-pack build --release --target web

Your Cargo.toml should have release optimizations:

[profile.release]
opt-level = "s"       # "s" = small, "z" = smallest
lto = true            # Link-time optimization
codegen-units = 1     # Better optimization
strip = true          # Remove debug symbols
panic = "abort"       # Smaller panic handling

Optimize Binary Size

Step 1: wasm-opt

# Install binaryen
npm install -g binaryen

# Optimize (can reduce size 10-30%)
wasm-opt -Oz -o optimized.wasm pkg/my_app_bg.wasm

Step 2: Measure what's taking space

# Install twiggy
cargo install twiggy

# See top functions by size
twiggy top pkg/my_app_bg.wasm

# See call graph
twiggy paths pkg/my_app_bg.wasm

Step 3: Remove unnecessary code

// Use #[cfg] to exclude code from Wasm builds
#[cfg(not(target_arch = "wasm32"))]
fn native_only() { /* ... */ }

// Avoid pulling in large crates for small features
// Use feature flags to minimize dependencies

Serving Wasm Correctly

Your server must send the correct MIME type:

File Content-Type
.wasm application/wasm
.js application/javascript

Most modern hosts handle this automatically. If not, add headers:

# _headers file (Cloudflare Pages, Netlify)
/*.wasm
  Content-Type: application/wasm
  Cache-Control: public, max-age=31536000, immutable

Deploy to Cloudflare Pages

# Build
wasm-pack build --release --target web

# Create a dist/ folder with your HTML + pkg/
mkdir -p dist
cp index.html dist/
cp -r pkg dist/

# Deploy
npx wrangler pages deploy dist/ --project-name my-wasm-app

Or connect your GitHub repo for automatic deploys on every push.

Deploy to Vercel

// vercel.json
{
  "headers": [
    {
      "source": "/(.*).wasm",
      "headers": [
        { "key": "Content-Type", "value": "application/wasm" },
        { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
      ]
    }
  ]
}

Deploy to Netlify

# netlify.toml
[[headers]]
  for = "/*.wasm"
  [headers.values]
    Content-Type = "application/wasm"
    Cache-Control = "public, max-age=31536000, immutable"

Caching Strategy

Wasm binaries are immutable once built — use aggressive caching:

# Cache .wasm for 1 year (content-hashed filename)
Cache-Control: public, max-age=31536000, immutable

# Don't cache the JS loader (might reference new .wasm)
Cache-Control: public, max-age=3600

Loading Strategy

Streaming compilation (fastest)

// Browser compiles Wasm while downloading — fastest possible
const response = await fetch('app_bg.wasm');
const module = await WebAssembly.compileStreaming(response);

Lazy loading

// Only load Wasm when needed
let wasmModule = null;

async function getWasm() {
    if (!wasmModule) {
        wasmModule = await import('./pkg/my_app.js');
        await wasmModule.default();
    }
    return wasmModule;
}

// Load on first use
button.onclick = async () => {
    const wasm = await getWasm();
    wasm.process(data);
};

Production Checklist

Step Command Why
Release build wasm-pack build --release Remove debug info
wasm-opt wasm-opt -Oz Shrink binary 10-30%
Check size ls -lh pkg/*.wasm Target < 100KB
Test MIME type Check Network tab Must be application/wasm
Test HTTPS Required for Wasm Won't load over HTTP
Add caching Set Cache-Control headers 1 year for .wasm files
Error reporting Add console_error_panic_hook Debug production crashes
Fallback Check WebAssembly exists Show message if unsupported

Try It

Click Run to see the deployment checklist. Follow these steps when you're ready to ship your Rust/Wasm project.

Try It

Chapter Quiz

Pass all questions to complete this lesson