The incident report always reads the same way: a user opens the app on version 1, the team deploys version 2 mid-session, and the user clicks something. The frontend sends a request expecting the version 1 API, and the response comes back in a shape it can't parse. This failure is the canonical version skew scenario.
Teams on Vercel deploy over 6 million times per month. At that frequency, two live versions of an app coexisting in the wild is the normal state between any two requests. Which API versioning strategy you should use depends on who consumes the API, and on whether the deployment layer can absorb the skew before you reach for a /v2 path.
Copy link to headingKey takeaways
Skew Protection routes supported framework-managed requests back to the deployment that served the initial page load by attaching a deployment ID, helping prevent client-to-server version mismatches without requiring you to manually version those requests.
Header-based and content-negotiation versioning fragment edge caches; the Vary header can cause caches to include specified request headers in the cache key, while URI-path versioning puts the version in the URL path, a common component of CDN cache keys.
Our own REST API uses URL-path versioning (
api.vercel.com/v6/) because external consumers need a stable contract they can pin.Skew Protection stops at the frontend boundary; services behind the frontend must remain compatible with calls from older clients.
Stripe auto-pins tenants to a version until they explicitly upgrade, while Shopify force-forwards retired versions, and that single design choice determines who pays the migration cost.
RFC 9745 (the Deprecation header) and RFC 8594 (the Sunset header) give any versioning strategy a standards-based deprecation signal without inventing custom headers.
Copy link to headingAt a glance: which strategy fits which API boundary
Who consumes the API decides the strategy. A same-team full-stack app can push the compatibility burden onto deployment infrastructure; a partner or public API needs an explicit contract the consumer controls.
Copy link to headingThe five canonical strategies and what they actually trade off
These are the explicit schemes, and their trade-offs shift when the API runs behind an edge CDN instead of a single origin server.
Copy link to headingURI-path versioning
The version lives in the URL (/api/v1/users). It's the most discoverable option for clients, and because the URL is the cache key, CDN caching works without any Vary gymnastics. The cost is URI proliferation: every breaking change spawns a parallel set of endpoints to maintain. Google's AIP-185 contains this by updating channels in place, with a stable v1 and a v1beta that must be a superset of the stable channel's functionality, instead of minting a v2 for every change.
Copy link to headingQuery parameter and header versioning
Microsoft Azure requires an api-version query parameter in YYYY-MM-DD format; omitting it returns HTTP 400. Custom headers like x-api-version: 2 keep URLs clean but hide the version from browser tooling and CDN routing. Both push the version signal out of the URL, which carries a caching penalty at the edge.
Copy link to headingDate-based versioning
Stripe identifies versions by release date, currently 2026-06-24.dahlia. An account is auto-pinned to the latest version at its first API call and stays there until an explicit upgrade. Breaking changes cluster into named major releases; monthly releases within a major are backward-compatible only. Stripe built an upgrader/downgrader layer where all code targets the latest version. Old requests are converted on the way in, and new responses are converted back to the old format on the way out, and that translation layer is the price Stripe pays for auto-pinning tenants.
Copy link to headingVersionless / continuous evolution
GraphQL's official position is that because clients request only the fields they need, additive changes are non-breaking by default, and the @deprecated directive retires fields without a hard cutover. Daniel Hai's critique of "versionless" GraphQL argues it works for close partner integrations or APIs with few consumers and low stability expectations; for large-scale public APIs, removing or renaming a field is still a breaking change, and the versionless approach does not cover those cases.
Copy link to headingVercel's platform makes client-server versioning optional
For a full-stack app where the same engineers own the frontend and the API, explicit URL versioning usually solves a problem the deployment layer can solve more cheaply. We built Skew Protection on that bet.
The mechanism rides on immutable deployments. On initial page load, the deployment ID is encoded in the HTML. Every subsequent managed request from that session carries the ID via a ?dpl= query parameter or an x-deployment-id header, and our edge routes those tagged requests back to the exact deployment that served the initial load. The old deployment never changed, so it can keep answering.
Skew Protection has been on by default for new Pro and Enterprise projects since November 19, 2024, and on November 6, 2025, the max age became configurable up to the full lifetime of your deployments, removing the previous limits of 12 hours on Pro and 7 days on Enterprise.
We're equally direct about where the guarantee ends: Skew Protection eliminates the hardest-to-manage skew boundary between the user's client and the servers. Services behind the frontend server must continue to be compatible with calls from older clients. There's a smaller gap, too: custom fetch() calls from client components require manual pinning, so you pass the deployment ID yourself for those.
Vercel Services, announced June 30, 2026, extends the same logic behind the frontend with an atomic deployment model for the full-stack app. The parts deploy and roll back together, which keeps layers in sync and avoids explicit cross-service version negotiation.
Copy link to headingHeader-based versioning breaks your edge cache
If your API runs on an edge CDN and you version via custom headers or content negotiation, you're probably destroying your cache hit rate. Benchmarks against a local server never surface this cost.
Correct HTTP caching with header-based versioning requires a Vary header listing the version header as a cache dimension, and CDNs create a separate cache entry for each unique combination of Vary values. A high-cardinality version header fragments the cache toward a near-zero hit rate. Akamai goes further: its servers cannot cache objects whose Vary header contains any value other than Accept-Encoding. Responses with any other Vary value skip the cache entirely.
Our global network caches at the URL level, so URI-path versioning maps /api/v1/ and /api/v2/ to distinct cache keys with no Vary fragmentation at all. This URL-level caching is why our own REST API versions in the path (api.vercel.com/v6/), and why the table above rates every header-based scheme poor for edge cacheability. One clarification: Vary: Accept-Encoding is fine and expected, since compression-variant caching is exactly what it's for. The damage comes from adding version headers to Vary.
Copy link to headingPublic APIs and service-to-service calls still need explicit versions
Skew Protection solves client-to-BFF skew. External consumers who pin a version, and microservices behind the frontend that call each other, still need an explicit contract. For those boundaries, URI-path versioning at the route-handler level is the right starting point, and the two dominant multi-tenant models are worth comparing before you pick one.
Copy link to headingMulti-tenant versioning: Stripe vs. Shopify
Stripe's model protects tenants from silent breakage at the cost of that internal compatibility layer. Shopify's model keeps the server-side simpler and shifts migration urgency onto app developers, so with either model, you are choosing who absorbs the migration cost.
Copy link to headingImplementing URI-path versioning in Next.js
Next.js routes by file path, so app/api/v1/route.ts handles /api/v1 and app/api/v2/route.ts handles /api/v2, and bracket notation covers dynamic segments. To inspect a version header inside a handler, the headers() helper from next/headers reads x-api-version without any middleware. For programmatic routing at the edge, say, sending beta users to a v2 handler, use Routing Middleware with NextResponse.rewrite() or vercel.json rewrites with arrays matching the version header. The previous section's Vary caveat still applies.
Copy link to headingCommunicating deprecations with HTTP standards
RFC 9745, published as a Proposed Standard in March 2025, defines the Deprecation response header, the first stable RFC for this signal. Pair it with RFC 8594's Sunset header (Informational, May 2019) to say both that a resource is deprecated and when it goes dark. In practice, your route handler sets Deprecation and something like Sunset: Sat, 01 Jan 2028 00:00:00 GMT, and your API client layer logs and monitors those headers so deprecation events surface before they become incidents. For field-level signals within a single response, the application/deprecations+json Deprecation Manifest draft, submitted June 26, 2026, fills the gap the resource-level RFCs leave open.
Copy link to headingMatch the versioning layer to the API's blast radius
How far a breaking change reaches decides which layer you version at. If the same team owns the client and server, the app runs on Vercel, and the break only affects the browser session, Skew Protection absorbs the break at the deployment layer. Partner apps and third-party integrations need a stable, explicit contract. Public API users do too. URI-path versioning is the default because it preserves edge cacheability and gives clients the most visibility.
If you run a multi-tenant platform, the Stripe-versus-Shopify choice from earlier decides who absorbs the migration cost. We live with our own version of that trade-off. Our public API keeps external contracts stable through path versioning and advance notice of backward-incompatible changes, and we absorb the internal complexity so consumers do not have to.
Copy link to headingFAQ
Q: Does Skew Protection work for API routes as well as page requests?
A: Yes. For supported frameworks, framework-managed client requests include the deployment ID and resolve to the same deployment that served the initial page; custom client fetch() calls, including fetches to your own Next.js route handlers, are not automatically pinned unless you pass the deployment ID yourself or use session-cookie pinning. Custom fetch() calls from client components require manual pinning; pass the deployment ID via ?dpl= or x-deployment-id.
Q: Which versioning strategy works best with Vercel's edge caching?
A: URI-path versioning. Each version gets its own URL, and the URL is the cache key. Header-based schemes typically need Vary so shared caches distinguish responses by request header, which can fragment the cache by creating additional cache entries and more misses.
Q: What plans include Skew Protection?
A: Pro and Enterprise, and it's been on by default for new projects since November 19, 2024.
Q: Is there a standard HTTP header for announcing API deprecation?
A: Yes. RFC 9745 (March 2025) defines the Deprecation response header; combine it with RFC 8594's Sunset header to signal both the deprecation and the shutdown date.
Q: If I use Rolling Releases or blue-green deployments, do I still need API versioning?
A: Rolling Releases shift a configurable fraction of traffic to a new deployment while Skew Protection keeps each session pinned to one version, covering the client-server boundary. Calls between backend services still need explicit versioning or forward/backward compatibility, because Skew Protection stops at the frontend layer.