SYSTEM Cited by 1 source
Vercel Routing Service¶
Definition¶
Vercel Routing Service is the global, single-threaded
front door that handles every incoming HTTP request to every
Vercel deployment — routing requests to the right build
output (static asset, page, API route, webpack chunk, Next.js
route segment) or returning 404 Not Found when the path
doesn't exist.
The service is not a product surface Vercel markets; it's an infrastructure layer that sits in front of Edge Functions, Vercel Functions, and the object-storage tier holding static build outputs. Every Vercel URL on every customer deployment flows through it.
Architectural role¶
Client ──► DNS ──► Edge POP ──► Routing Service ──┬─► Static-asset storage (common)
├─► Edge Function
├─► Vercel Function
└─► 404 (path doesn't exist)
The routing service decides which of the four branches each request takes. To do so, it needs to know — for the specific deployment being routed to — what paths exist.
Per deployment, the build service generates a list of
every path the deployment serves: static assets under
/public, rendered pages under /pages and /app, API
handlers, webpack chunks, Next.js route segments. Historically
this list was serialised as a JSON tree of paths uploaded
to storage and parsed per routing-service instance on
deployment update.
The 2026-04-21 Bloom-filter migration¶
As of 2026-04-21, the routing service replaces the JSON-tree path-lookup substrate with a Bloom filter — a compact probabilistic set-membership structure whose query cost is O(k) hashes per lookup rather than O(path-tree-depth) JSON object traversals, and whose construction cost is O(file-bytes-read) rather than O(parsed-objects-allocated).
The move delivered:
- Path-lookup p99: ~100 ms → ~0.5 ms (~200×).
- Path-lookup p999: ~250 ms → ~2.4 ms (~100×).
- Routing-service heap & memory: baseline − 15 %.
- Aggregate TTFB p75–p99 across all routed requests: baseline × 0.9 (10 % improvement).
- Path-lookup file size (Base64-encoded): 70–80 % smaller.
The aggregate TTFB improvement reached traffic to every deployment, not just heavy-site tenants — because the previous JSON-parse cost on heavy-site path-lookup files had been stealing event-loop time from everyone else on the same reactor via event-loop blocking.
(Source: sources/2026-04-21-vercel-how-we-made-global-routing-faster-with-bloom-filters.)
Implementation notes¶
Single-threaded runtime¶
The code snippet in the 2026-04-21 post is LuaJIT / FFI, strongly suggesting the routing service runs on an OpenResty-style nginx + LuaJIT stack — the canonical choice for high-throughput, single-threaded, request-routing edge services at scale (Cloudflare's pre-Workers edge ran on the same substrate).
Two-service coordination¶
The build service (language not disclosed) generates Bloom filters; the routing service (LuaJIT) queries them. Both sides implement matching Bloom-filter logic — same hash family, same bit-indexing, same parameter interpretation — so that a filter generated by one is byte-for-byte query- compatible in the other. Vercel chose to hand-implement matching logic in both codebases rather than rely on a shared library.
JSONL file format¶
Bloom filters ship as two-line JSONL files:
{"version":"test","bloom":{"n":10,"p":1e-7,"m":336,"k":23,"s":0}}
"0kxC4anU4awVOYSs54vsAL7gBNGK/PrLjKrAJRil64mMxmiig1S+jqyC"
Line 1 = parameters (element count, FP rate, bit-array size, hash count, seed); line 2 = Base64-encoded bit array. See patterns/jsonl-parameters-plus-base64-payload.
Base64 as byte buffer¶
The routing service treats the Base64-encoded bit array as a byte buffer, decoding sextets on demand during membership queries rather than materialising a decoded byte string. See concepts/base64-as-byte-buffer-inline. This is the load-bearing performance move — without it, the per-request decode would re-introduce the string-allocation cost the migration was supposed to eliminate.
Rollout¶
Gradual. Deployments made before the Bloom-filter shipped continue to use the legacy JSON path-lookup. Newer deployments use the Bloom-filter path. The shape of the rollout is not named; the inflection-point deployment per project is "the project's next deploy."
Workload shape¶
Vercel's description of the traffic pattern before the migration:
- Majority of apps: path-lookup parse under 1 ms; membership test sub-millisecond.
- p90: ~4 ms lookup.
- p99: ~100 ms lookup — dominated by a handful of heavy-site tenants.
- p999: ~250 ms lookup — same heavy-site long tail.
Trigger workloads named explicitly:
- E-commerce sites with large product catalogues (hundreds of thousands of product URLs).
- Documentation sites with thousands of pages.
- Applications with dynamic routing that enumerate many routes at build time.
The long tail of heavy-site tenants is a canonical pattern — a few customers with outsized path counts degrading every other tenant's latency through shared-reactor blocking.
Security role¶
The pre-storage membership check is also the routing service's enumeration-attack defence (concepts/path-enumeration-attack):
"This prevents unnecessary requests to storage and protects against enumeration attacks, where attackers try to discover hidden files by guessing URLs."
Fast, uniform-response 404s for non-existent paths deny attackers the latency / error-shape side-channels they'd otherwise use to probe a deployment's directory structure.
Related systems and primitives¶
- systems/vercel-cdn — sister edge system. The routing service decides which backend a request should hit (static / edge function / Vercel function / 404); the CDN handles cache semantics, request collapsing, and response delivery once a real path is confirmed. Both consume deployment metadata at the edge — routing service ingests a Bloom filter of paths; CDN ingests per-route cacheability (ISR / SSG / dynamic). Both are zero-config for developers.
- systems/vercel-edge-functions — V8-isolate-based edge runtime; one of the downstream targets the routing service dispatches to.
- systems/vercel-functions — general serverless function runtime on Fluid compute; another downstream target.
- systems/vercel-botid — edge bot-detection product; runs in the routing tier alongside path lookup.
- systems/vercel-edge-functions / Edge Middleware — user-writable request interceptors that run in the same edge tier; coexist with the routing service's own dispatcher.
Undisclosed / out of scope¶
- Language stack. The post never names the routing service's runtime (LuaJIT is inferred from the FFI code snippet); the build service's is also unnamed.
- Hash family used by the Bloom filter.
- Per-POP vs centralised. The routing service is single-threaded per process; how many replicas, where they're physically placed, and what the consistency model is for Bloom-filter updates across replicas — not disclosed.
- Filter lifecycle. Filters are built at deploy time, but the cache-invalidation / replica-reload model is not disclosed. A filter is assumed to be invalidated by any deployment change to the path list.
- Fallback on FP. On a Bloom-filter positive match, the
routing service fetches from storage, which confirms
whether the path actually exists. If not (a false
positive), the response is
404. The per-request cost of an FP is one storage roundtrip. - Interaction with CDN cache. Static assets are presumably cached in a CDN tier; whether the routing service is in front of or behind the CDN for static assets is not clarified.
- Interaction with rewrites / redirects. Vercel supports
rewrites (
vercel.json) that map one URL to another. Whether rewrites resolve before or after the Bloom- filter check is not disclosed.
Seen in¶
- sources/2026-04-21-vercel-how-we-made-global-routing-faster-with-bloom-filters — Canonical wiki introduction of the routing service as a named system. Discloses the Bloom-filter substitution, the JSON-parse-as-event-loop-blocker diagnosis, the LuaJIT + FFI implementation hint, the two-service coordination requirement, the JSONL file format, the Base64-as-buffer optimisation, and all the operational numbers (200× p99, 100× p999, 15 % memory drop, 10 % TTFB improvement, 70–80 % file-size reduction).