What is WebAssembly?#
WebAssembly (WASM) is a binary instruction format that runs inside the browser at near-native speed. Think of it as a portable compilation target: you write code in Rust, C, C++, or Go, compile it to .wasm, and the browser executes it directly in a sandboxed VM alongside JavaScript.
The browser has always been able to run computation, but JavaScript is an interpreted, dynamically typed language. It is fast enough for most tasks, but not for video encoding, image processing, cryptography, or physics simulations. Before WASM, these workloads had to live on a server. Now they can live in the browser tab.
How it Works#
WASM is not a language. It is a compilation target with four core types (i32, i64, f32, f64) and a stack-based virtual machine. When you load a .wasm module, the JS engine compiles it to native machine code via JIT — starting from a much lower-level representation that maps directly to hardware.
sequenceDiagram
participant Browser
participant WASM VM
participant JS
Browser->>WASM VM: Load .wasm module (fetch)
WASM VM->>WASM VM: JIT compile to native code
JS->>WASM VM: instantiate + share Memory buffer
JS->>WASM VM: call exported function(inputPtr, len)
WASM VM->>WASM VM: process bytes in linear memory
WASM VM-->>JS: return outputPtr
JS-->>Browser: read result from shared buffer
The memory model is explicit: WASM gets a linear WebAssembly.Memory buffer, and JS and WASM communicate by reading and writing into it. No garbage collection crossing boundaries, no serialization overhead.
// Load and instantiate a WASM module from JavaScript
const { instance } = await WebAssembly.instantiateStreaming(
fetch('/converter.wasm'),
{ env: { memory: new WebAssembly.Memory({ initial: 256 }) } }
);
// Call exported WASM functions directly via shared memory
const result = instance.exports.convert_image(inputPtr, inputLen, outputPtr);The Practical Upside#
The most important consequence is zero server round-trips for CPU-bound work. If you are converting an image, the file never leaves the browser. No upload latency, no server costs, no privacy concerns. The user’s CPU does the work.
This changes the economics of entire categories of tools. A workload that previously required a fleet of servers now requires only static file hosting.
VERT: 250+ Format Conversions, Fully Local#
VERT is an open-source file converter built with SvelteKit and TypeScript. Under the hood it shells out to compiled WASM modules (FFmpeg, ImageMagick, and others) to convert images, audio, and documents entirely in the browser.
| Feature | Details |
|---|---|
| File size limits | None — constrained by your RAM, not a server quota |
| Privacy | Files never leave the browser |
| Formats supported | 250+ across images, audio, documents |
| Video conversion | Requires self-hosted companion daemon |
| Hosting | Pre-built static site, self-hostable |
Hosting on CloudFront + S3 at Zero Cost#
VERT’s build output is a standard static site: HTML, JS, CSS, and .wasm files. The WASM binaries are large (FFmpeg compiled to WASM weighs several megabytes), so the right hosting choice is a CDN that serves them from an edge cache close to the user.
CloudFront has a permanent free tier: 1 TB of data transfer per month and 10 million HTTP/S requests per month — no expiry.
flowchart LR
User([User Browser]) -->|HTTPS| CF[CloudFront Edge]
CF -->|Cache miss only| S3[(S3 Bucket\nprivate)]
CF -.->|Cached .wasm, .js, .css| User
subgraph AWS
CF
S3
OAC[Origin Access Control]
end
OAC -- SigV4 signing --> S3
CF -- uses --> OAC
Do not enable static website hosting on the S3 bucket. Serving through CloudFront with an Origin Access Control (OAC) is both more secure and cheaper — the bucket stays fully private.
Step 1: Build VERT#
git clone https://github.com/VERT-sh/VERT.git
cd VERT
npm install
npm run buildThe output lands in build/. It is a fully self-contained static site.
Step 2: Create an S3 Bucket#
aws s3 mb s3://my-vert-instance --region eu-central-1
# Block all public access — CloudFront is the only entry point
aws s3api put-public-access-block \
--bucket my-vert-instance \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"Step 3: Upload the Build#
# Hashed assets: cache forever
aws s3 sync build/ s3://my-vert-instance/ \
--delete \
--cache-control "public, max-age=31536000, immutable"
# Entry points: always revalidate
aws s3 cp build/index.html s3://my-vert-instance/index.html \
--cache-control "public, max-age=0, must-revalidate"The immutable directive tells CloudFront and the browser that hashed assets like _app/immutable/chunks/index.abc123.js will never change for a given URL — maximizing cache hit rate.
Step 4: CloudFront Distribution#
Create an OAC so CloudFront can read from the private S3 bucket:
aws cloudfront create-origin-access-control \
--origin-access-control-config '{
"Name": "vert-oac",
"OriginAccessControlOriginType": "s3",
"SigningBehavior": "always",
"SigningProtocol": "sigv4"
}'Then create the distribution, using the AWS-managed CachingOptimized cache policy:
{
"Origins": {
"Items": [{
"Id": "s3-vert",
"DomainName": "my-vert-instance.s3.eu-central-1.amazonaws.com",
"S3OriginConfig": { "OriginAccessIdentity": "" },
"OriginAccessControlId": "<OAC_ID>"
}]
},
"DefaultCacheBehavior": {
"ViewerProtocolPolicy": "redirect-to-https",
"CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
"Compress": true
},
"CustomErrorResponses": {
"Items": [{
"ErrorCode": 403,
"ResponsePagePath": "/index.html",
"ResponseCode": "200"
}]
},
"DefaultRootObject": "index.html"
}The CustomErrorResponses block is critical. SvelteKit uses client-side routing, so any path CloudFront does not find in S3 returns a 403. Mapping that 403 to index.html with a 200 response lets the SvelteKit router take over.
Finally, allow the OAC to read from S3:
{
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "cloudfront.amazonaws.com" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-vert-instance/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::<ACCOUNT_ID>:distribution/<DIST_ID>"
}
}
}]
}Cost Breakdown#
| Resource | Free tier | Cost after free tier |
|---|---|---|
| S3 storage (~300 MB) | 5 GB/month (12 months) | ~$0.007/month |
| CloudFront transfer | 1 TB/month (permanent) | $0 |
| CloudFront requests | 10M/month (permanent) | $0 |
| ACM certificate | Free | $0 |
For personal use, this is effectively free indefinitely. After the S3 free tier expires, you are looking at less than a cent a month in storage.
Why This Pattern Works#
The reason the cost is zero is the same reason WASM makes VERT possible in the first place: all computation is pushed to the client. The server (CloudFront + S3) only needs to serve static bytes once. After the first load, the browser caches the WASM modules aggressively. Subsequent visits are served from the local disk cache.
A traditional file converter would need a fleet of worker instances processing uploads 24/7. You would pay for compute, storage, egress, and queue infrastructure. With WASM, your infrastructure cost collapses to “store and deliver static files” — which AWS essentially gives away at personal-use scale.
WASM is not just a performance trick. It changes the infrastructure model entirely. Any CPU-bound tool that previously required server compute can be re-architected as a static site with zero ongoing costs.