Deploying Wasm to Vercel and Netlify Edge Functions
This page answers one task: you want a WebAssembly module — an image transformer, a Markdown renderer, a geolocation lookup, an A/B assignment engine — running in edge functions on Vercel or Netlify, close to users, and you need to know how modules are imported on each platform, what the limits are, and when Node-based serverless functions are the better choice.
Prerequisites
- [ ] A Wasm module with glue that does not depend on Node APIs (or a raw module you instantiate yourself).
- [ ] A Vercel or Netlify project and the platform’s CLI for local development.
- [ ] An idea of the module’s size and memory needs.
The edge runtime model
Both platforms run edge functions on V8 isolates rather than full Node processes. Netlify Edge Functions are built on Deno; Vercel’s Edge Runtime is a
standards-based runtime exposing web APIs (fetch, Request, Response, streams, Web Crypto). Isolates start in milliseconds and run near users, but they offer
web APIs rather than Node’s (fs, child_process and most Node built-ins are unavailable or limited), cap bundle size and memory, and limit execution time per
request.
WebAssembly fits this model well: modules are portable, start quickly, and need no native binaries. The packaging differs from Node: instead of reading a
.wasm file from disk, edge bundlers let you import the module so it is included in the deployment and compiled by the runtime.
Step 1 — import the module on Vercel
In Vercel’s Edge Runtime, import the .wasm file with the ?module suffix, which yields a compiled WebAssembly.Module:
// app/api/render/route.ts (Next.js App Router) or api/render.ts
export const runtime = "edge";
import wasmModule from "../../wasm/md_bg.wasm?module";
import { initSync, render } from "../../wasm/md.js";
initSync({ module: wasmModule }); // once per isolate, at module scope
export async function POST(req: Request) {
const { markdown } = await req.json();
return new Response(render(markdown), { headers: { "Content-Type": "text/html; charset=utf-8" } });
}
Initialising at module scope means each isolate instantiates the module once and reuses it for every request it serves. Use glue built for the web
(--target web from wasm-pack) so it does not reach for Node APIs.
Step 2 — import the module on Netlify
Netlify Edge Functions run on Deno, which can import .wasm files and supports standard fetch-based loading. A portable approach that works across Deno
versions reads the module bytes from a URL relative to the function and instantiates once:
// netlify/edge-functions/render.ts
import { initSync, render } from "./wasm/md.js";
const bytes = await Deno.readFile(new URL("./wasm/md_bg.wasm", import.meta.url));
initSync({ module: new WebAssembly.Module(bytes) });
export default async (req: Request) => {
const { markdown } = await req.json();
return new Response(render(markdown), { headers: { "content-type": "text/html; charset=utf-8" } });
};
export const config = { path: "/api/render" };
Check the platform documentation for the current supported way to include and read binary assets in edge functions — bundling behaviour has changed over time, and some setups prefer importing the module directly with Deno’s Wasm import support.
Step 3 — respect limits
Edge functions have limits on compressed bundle size (a few megabytes on common plans; check current values), memory per isolate (often around 128 MB),
and CPU or wall time per request. A Wasm module plus glue must fit within the bundle limit along with your code; optimise for size (opt-level = "z",
wasm-opt -Oz, stripping debug info). Memory limits include linear memory: a module that grows to 200 MB fails. CPU time limits rule out heavy processing —
resizing large images or running ML models may exceed them; such work belongs in Node serverless functions or dedicated services.
Step 4 — stream responses where possible
Edge runtimes support streaming responses with ReadableStream. For outputs produced incrementally — rendering long documents, transforming large uploads —
stream from the Wasm module’s incremental API rather than buffering everything, which lowers time to first byte and memory use.
Step 5 — test locally with each CLI
Use vercel dev and netlify dev (or netlify functions:serve) to run edge functions locally in an environment close to production. Test with realistic
inputs, check memory and timing, and verify the module loads the same way it will when deployed — local Node-based testing can hide edge-runtime incompatibilities
such as accidental Node API use in glue code.
Choosing edge or Node functions per route
Use edge functions for latency-sensitive, light work: personalisation, A/B assignment, request validation, small renders, signature checks. Use Node serverless functions for heavy or Node-dependent work: large image processing, PDF generation, ML inference with large models. The same Wasm module can serve both if its glue works in either environment; route by workload rather than forcing one runtime to do everything.
Middleware use cases
Both platforms run edge code as middleware in front of pages, which is where small Wasm modules are especially useful: verifying signed cookies or JWTs with a compact signature-verification module, evaluating feature-flag or A/B rules shared with the client, rewriting requests based on a geolocation or device-detection table, or validating request bodies against the same schema logic the frontend uses. Middleware runs on every matching request, so the module’s per-request cost and the isolate’s cold start matter more than for occasional API routes; keep middleware modules small, do the minimum work needed to decide, and avoid network calls from middleware where a local lookup will do. Restrict middleware to the paths that need it with the platform’s matcher configuration, so static assets are not routed through Wasm unnecessarily.
Portability between platforms
Writing the handler logic against web standards — Request, Response, streams, Web Crypto — and keeping platform-specific code to the module-loading lines
makes it easy to move between Vercel, Netlify, Cloudflare and Deno Deploy. Put the Wasm module and its web-targeted glue in a small package with an
init(moduleOrBytes) function, and give each platform a few lines of adapter code that obtains the module the platform’s way. The same package can then be
tested in Deno or Node with web APIs, which is faster than deploying to test.
Observability at the edge
Edge logs are often sampled and short-lived. Log Wasm initialisation failures explicitly, record the module version in each log line, and send error reports to your tracker from the handler’s catch block; otherwise a bundling mistake shows up only as generic 500 responses.
Expected output
A Markdown-rendering route runs as a Vercel Edge Function with the module imported via ?module and initialised once per isolate; the Netlify version reads the
module from the function bundle and instantiates at module scope; both respond in under 20 ms at the edge for typical documents; the bundle stays under the
plan’s size limit at 410 KB compressed; and heavy image routes remain Node functions.
Gotchas
- Node-targeted glue in edge runtimes.
fsandrequireare unavailable. Use web-targeted glue. - Instantiating per request. Wasted CPU. Instantiate at module scope.
- Ignoring bundle and memory limits. Deployments fail or requests crash. Optimise and measure.
- Heavy work at the edge. CPU limits cut requests short. Use Node functions or services.
- Testing only in Node. Edge-only incompatibilities appear after deploy. Use the platform CLI.
- Silent initialisation failures. Bundling mistakes appear as generic 500s. Log init errors with the module version.
Performance note
Rendering a typical 20 KB Markdown document took about 3 ms in the edge function after the isolate was warm; isolate cold start including module compilation added roughly 30–60 ms for a 400 KB module.
Frequently Asked Questions
Can edge functions use WASI modules? Not directly; provide a WASI shim or compile for a browser-style target with explicit imports.
Do edge functions support threads?
No — isolates are single-threaded; there is no SharedArrayBuffer-based threading.
Can I cache compiled modules between requests? Within an isolate, yes — module-scope instances persist while the isolate lives.
Are limits the same on both platforms? No — check each platform’s current documentation for size, memory and time limits.
Is edge middleware a good place for Wasm? Yes, for small, fast checks such as signature verification or flag evaluation; keep modules small and restrict middleware to needed paths.
How do I keep the code portable between edge platforms? Write handlers against web standards and isolate module loading in a few lines of per-platform adapter code.
Should middleware run on static asset requests? No — use the platform’s matcher configuration so only routes that need the check pass through Wasm.
How should edge errors be reported? Catch errors in the handler, report them to your tracker with the module version, and log initialisation failures explicitly.
Can the same Wasm package run on both platforms?
Yes — keep it web-targeted with an init that accepts a module or bytes, and add a tiny adapter per platform.
Related
- Deploying Wasm to Cloudflare Workers — another isolate platform.
- Using Wasm in serverless Node functions — the Node alternative.
- Benchmarking cold starts across edge platforms — measuring startup.
- Using Wasm in a Next.js project — Next.js integration.
← Back to Serverless & Edge Deployment