Edge functions solve real problems — auth at the edge, personalization without origin round trips, request rewriting no rules engine can express — and they are the most proprietary surface in delivery: every platform ships its own runtime, its own APIs, its own state store, and code written naively for one is a rewrite for any other. You do not have to abstain. You have to decide, per capability, how much portability you are trading — and keep the receipts.
What edge compute is worth — and what it costs later
The value is concrete: logic that must run before the cache (token validation, A/B assignment, geo personalization, header surgery) executes at the edge in microseconds instead of costing an origin round trip, and things a rules engine cannot express become a few lines of JavaScript. The cost arrives later, denominated in options: every function deepens dependence on one runtime’s semantics, which weakens the multi-CDN scorecard’s portability question, narrows which traffic can ever fail over (a function-dependent hostname cannot move to a platform lacking the function), and hands the incumbent quiet leverage at renewal. The runtime landscape — isolates here, a WASM-first platform there, containers elsewhere, none API-compatible — is mapped in our edge-platforms field guide; this guide is about engaging with that landscape without marrying it.
The thin-edge principle: what belongs there
The single highest-leverage decision is what the edge is for. Keep it thin: the edge adapts requests and responses — validates tokens, normalizes URLs and headers, assigns variants, routes — while business logic, rendering decisions, and anything with domain rules stays at the origin, in your own services, portable by construction. The test for each candidate function: could a competent engineer describe its job in one sentence containing no business nouns? “Reject requests whose token fails HMAC” passes; “compute the customer’s shipping eligibility” fails and belongs behind the edge, not on it. Thin edges are also fast edges (less code, fewer dependencies inside tight CPU budgets) and testable edges — a pure request-in, request-out transform runs in any test harness. Most estates that feel locked in are estates where the edge quietly became a second application tier; the principle prevents the drift better than any abstraction retrofits it.
Code to standards, wrap the differences
The runtimes have been converging on web-standard interfaces — the fetch-handler model, Request/Response objects, URL and streams APIs from WinterCG-style standardization — and your portability strategy is to live on that converged surface. Concretely: write handlers against standard Request/Response semantics; isolate every platform-specific touch (the event signature, config bindings, cache APIs, logging) behind a small adapter file per platform, so business-relevant code imports your interface, never the vendor’s; and keep the logic in plain testable modules the adapters merely mount. Held to that shape, the same function deploys to multiple platforms with per-platform glue measured in dozens of lines — which is precisely what lets the config-sync pipeline treat functions like any other generated artifact, deployed to both platforms with parity tests proving equivalent behaviour at each edge. WASM widens the same door for non-JavaScript logic where platforms support it. The residue that will not port — a scheduling API here, a platform-only trigger there — is exactly what the ledger in section five exists to record.
State is the real lock: treat it as a cache
Code ports; data anchors. Edge state stores — KV, durable objects, edge databases — are the stickiest surface in the stack: distinct consistency models, no export symmetry, and migrations that make the code rewrite look trivial. The discipline: authoritative state lives in your systems; edge state is a rebuildable projection — a cache of feature flags, token key sets, routing tables — pushed from the origin and reconstructible on any platform from scratch. The test mirrors the session rule in failover design: if a platform’s state store vanished right now, is anything lost that the origin cannot repopulate in minutes? If yes, you have made the platform a database, and that decision deserves the same scrutiny as any database choice — made explicitly, priced, and entered in the ledger, not backed into via a convenient API. Personalization counters, carts, anything user-authoritative: origin. Derived, refreshable, loss-tolerant: edge, freely.
The lock-in ledger and the annual portability test
Zero lock-in is not the goal — some vendor-exclusive capabilities are worth their tie, and pretending otherwise just drives the dependencies underground. The goal is a current, honest ledger: every edge function and every platform-specific dependency it carries, one line each — capability, platform tie, portability status (portable / adapter-wrapped / exclusive), and the exit sketch (“rewrite on X’s runtime, ~2 weeks” / “no equivalent; traffic pinned”). Review it in the quarterly audit alongside everything else that drifts, and let it feed the failover map: hostnames carrying exclusive functions are marked non-movable, consciously. Then, annually, run the portability test for real: deploy your adapter-wrapped function set to the second platform (or a local standards runtime) and make the parity suite pass. The estates that keep leverage are not the ones that avoided edge compute — they are the ones where “we could move this in a fortnight” is a tested statement, sitting in a ledger, visible at every renewal.
