Vercel
Routing Middleware for any framework on Vercel. Install, configure, verify.
The Vercel SDK is Routing
Middleware: a root
middleware.ts that runs in the request path, before the cache, for any
framework on Vercel. It extracts request metadata, asks the decision service
for a decision, and either lets the request continue or returns a 403.
No proxy hop, no DNS change.
Install
npm install @d3-inc/d3-edge-vercel-middlewareCreate middleware.ts at the root of your project:
import { createD3Middleware, config } from '@d3-inc/d3-edge-vercel-middleware';
export default createD3Middleware();
export { config };Set two environment variables in your Vercel project. The key belongs in the secret store, not in code:
| Variable | Value |
|---|---|
POLICY_WORKER_URL | https://edge-api.d3.com/v1/decision |
POLICY_ADAPTER_KEY | The key from API keys (created here) |
Deploy, then verify it worked.
Already have a middleware.ts? Composing createD3Middleware()
with an existing middleware function isn't supported.
Tell us what your existing middleware does
and we'll help you sequence it.
Testing locally? Leave POLICY_WORKER_URL unset in local dev. The
middleware becomes a passthrough and marks every response with
x-d3-edge: disabled, so you can confirm it loads without making a
single network call. Set the variable only in the deployed environment.
Configuration
Every option can be set as an argument to createD3Middleware() or as
an environment variable; arguments win.
| Option | Env var | Type | Default | What it does |
|---|---|---|---|---|
policyWorkerUrl | POLICY_WORKER_URL | string (URL) | unset | Decision endpoint. Unset means passthrough: the middleware does nothing. |
adapterKey | POLICY_ADAPTER_KEY | string | unset | Your org's key, sent as the bearer on every decision call. Required for anything to be logged. |
timeoutMs | POLICY_TIMEOUT_MS | number (ms) | 150 | Hard budget for the decision call. |
failMode | FAIL_MODE | "open" | "closed" | open | open: pass traffic when the call fails. closed: block instead. |
The exported config matcher excludes Vercel's own build assets
(_next/static, _next/image, favicon.ico). Files you serve from
public/ are not excluded by default. If you have large uploads or
heavy static routes, extend the matcher to skip them; non-GET requests
have their body read to compute a content digest for signature
verification.
Fail-open is the default on purpose. The worst case D3 Edge adds to your
site is the timeout budget, never an outage. Only set FAIL_MODE=closed
if serving unclassified traffic is worse for you than serving a 403.
Response shape
Every response that passed through the middleware carries an
x-d3-edge header: disabled, pass, block, fail-open, or
fail-closed. A blocked request gets a 403 with a JSON body:
{ "blocked": true, "ruleId": "…", "tier": "…", "identity": "…" }ruleId is absent when the block came from your org's default action
rather than a specific rule. Don't assume it's always present.
Troubleshooting
Nothing shows up in the log. Check that POLICY_WORKER_URL is set in
the deployed environment; unset is silent passthrough by design. Then
check your Vercel logs. The middleware logs every decision failure,
prefixed policy call rejected: with the HTTP status and reason.
401 with unknown-key, expired, or revoked. The key is wrong,
past its expiry, or was revoked in the dashboard. Create a fresh one and
update the secret.
503 with not-configured. Our side, not yours: key verification
isn't ready for your org yet. If it persists more than a few minutes,
contact us.
Middleware doesn't run at all. It must be middleware.ts at the
project root with a default export. If you replaced the exported config
with your own matcher, confirm it covers the routes you're testing.