Skip to content

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

text
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

ts
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

bash
# Copies twilic-js/wasm/pkg → your project's wasm/pkg
pnpm sync-wasm

Run before dev and build. The playground uses scripts/sync-twilic-wasm.mjs.

2. Vite config

ts
// 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

ts
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:

ts
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 wasmInput

See playground/src/shims/ for browser-safe backend substitutions that exclude Node N-API loaders.

Cloudflare Workers + Hono

bash
pnpm add hono @twilic/hono @twilic/core
pnpm add -D wrangler

wrangler.toml:

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

ts
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);
});

See Examples — API response.

Client-side fetch from Workers

For Worker-to-origin calls with Twilic:

ts
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

FactorImpact
WASM module sizeAdds to cold start; amortize across many requests
init() at module scopeRuns once per isolate — preferred
init() per requestAvoid — re-initializes WASM every time
Batch encodingAmortizes 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:

bash
GITHUB_PAGES=true pnpm build

Common pitfalls

ProblemFix
backend not initializedCall await init() before first encode/decode
WASM MIME error in previewBundle WASM via Vite assetsInclude, not /public
?init import failsUse ?url + manual instantiate or wasmInput URL
N-API import in browser bundleUse src/shims/ pattern from playground
Stateful on HTTP routesUse stateless profiles only at edge

Local development

bash
# 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

Released under the CC-BY-4.0 License.