Edge & Cloudflare Workers
Deploy Twilic on Cloudflare Workers, Vite edge apps, and other WASM-only runtimes. This guide covers WASM initialization, bundling, and Hono integration — the most common production stack for Twilic at the edge.
Stack overview
Browser / Worker
└── @twilic/hono or @twilic/fetch
└── @twilic/core
└── twilic-wasm (WebAssembly)Node.js N-API is not available on Workers. Always use the WASM backend.
Minimal Worker setup
import { Hono } from "hono";
import { init } from "@twilic/core";
import { twilicParser, twilicResponse } from "@twilic/hono";
import wasmModule from "@twilic/core/wasm/twilic_wasm_bg.wasm";
// Initialize once at module scope (cold start)
await init({ prefer: "wasm", wasmInput: wasmModule });
const app = new Hono();
app.post("/metrics", twilicParser(), async (c) => {
return twilicResponse(c, { ok: true, received: c.var.twilicBody });
});
export default app;WASM bundling with Vite / Wrangler
The playground and twilic-js repos show the production-tested pattern:
1. Sync WASM artifacts
# Copies twilic-js/wasm/pkg → your project's wasm/pkg
pnpm sync-wasmRun before dev and build. The playground uses scripts/sync-twilic-wasm.mjs.
2. Vite config
// vite.config.ts
export default defineConfig({
assetsInclude: ["**/*.wasm"],
build: {
rolldownOptions: {
output: {
codeSplitting: true, // keep vendor chunks separate
},
},
},
});assetsInclude for *.wasm is required so Rolldown bundles wasm-pack output correctly.
3. Import WASM with ?url
import wasmUrl from "@twilic/core/wasm/twilic_wasm_bg.wasm?url";
await init({
prefer: "wasm",
wasmInput: wasmUrl,
});Do not use bare ?init imports with Rolldown — they omit wasm-bindgen JS imports and break initialization.
4. Manual instantiate (advanced)
The playground uses manual instantiateStreaming with explicit glue imports when bundler auto-init fails:
import wasmUrl from "./twilic_wasm_bg.wasm?url";
import * as glue from "./twilic_wasm_bg.js";
const response = fetch(wasmUrl);
const { instance } = await WebAssembly.instantiateStreaming(response, {
"./twilic_wasm_bg.js": glue,
});
// pass instance to init via wasmInputSee playground/src/shims/ for browser-safe backend substitutions that exclude Node N-API loaders.
Cloudflare Workers + Hono
pnpm add hono @twilic/hono @twilic/core
pnpm add -D wranglerwrangler.toml:
name = "twilic-api"
compatibility_date = "2024-01-01"
[assets]
directory = "./dist"Build with Vite (WASM bundled into dist/assets/), deploy with Wrangler. Use stateless Dynamic or Batch profiles only — not Stateful on HTTP.
Content negotiation at the edge
app.get("/users", async (c) => {
const users = await fetchUsers();
if (c.req.header("accept")?.includes("application/vnd.twilic")) {
return twilicResponse(c, users);
}
return c.json(users);
});Client-side fetch from Workers
For Worker-to-origin calls with Twilic:
import { init } from "@twilic/core";
import { twilicFetchJson } from "@twilic/fetch";
import wasmUrl from "@twilic/core/wasm/twilic_wasm_bg.wasm?url";
await init({ prefer: "wasm", wasmInput: wasmUrl });
const data = await twilicFetchJson("https://api.example.com/users", {
headers: { Accept: "application/vnd.twilic" },
});Cold start considerations
| Factor | Impact |
|---|---|
| WASM module size | Adds to cold start; amortize across many requests |
init() at module scope | Runs once per isolate — preferred |
init() per request | Avoid — re-initializes WASM every time |
| Batch encoding | Amortizes encoder overhead across records |
Measure bundle size with @twilic/core CI tooling (twilic-js/scripts/measure-bundle.mjs).
GitHub Pages / static hosting
The playground deploys to GitHub Pages with GITHUB_PAGES=true at build time for correct asset base paths. Same pattern applies to any static Twilic SPA:
GITHUB_PAGES=true pnpm buildCommon pitfalls
| Problem | Fix |
|---|---|
backend not initialized | Call await init() before first encode/decode |
| WASM MIME error in preview | Bundle WASM via Vite assetsInclude, not /public |
?init import fails | Use ?url + manual instantiate or wasmInput URL |
| N-API import in browser bundle | Use src/shims/ pattern from playground |
| Stateful on HTTP routes | Use stateless profiles only at edge |
Local development
# Terminal 1 — build twilic-js WASM first
cd twilic-js && pnpm build:wasm && pnpm build:ts
# Terminal 2 — your edge app
cd your-app && pnpm dev