Vercel SDK migration: seven breaking-change families inside the v1.28 minor series
vercel sdk migration 1834 tracked change records
Most SDK migrations announce themselves with a major version. The Vercel SDK's v1.28 series did not. Between v1.27.0 and v1.28.14 — a run of patch-level releases inside one minor — the OpenAPI surface behind @vercel/sdk dropped response fields, withdrew a request flag, reshaped a scalar into an array, widened a path parameter, moved fields off a single-resource read entirely, and renamed the whole /v1/edge-config resource family to /v1/global-config. Every one of these breaks real consuming code, and none of them is the kind of change a lockfile bump warns you about. That is the profile this tool exists for: changes detected by diffing the vendor's own OAS snapshots, each mapped to a codemod you can preview as a diff.
The seven packs, exactly as the registry ships them
This list is read directly from the mendapi migration pack registry at page build time, so it matches what the codemods actually do:
Vercel Web Analytics: dheCipherSuite response dimension discontinued (v1.28.9)
Pack vercel-web-analytics-dhe-cipher-suite-removal:
- Remove dheCipherSuite reads (destructuring, dimension lists, object properties, and consuming statements) from web-analytics query consumers
- Remove unreferenced dheCipherSuite bindings from flat destructuring patterns (AST track)
Vercel Projects: enableFunctionsExtendedMaxDuration flag withdrawn from the API surface (v1.28.0)
Pack vercel-project-extended-max-duration-removal:
- Remove enableFunctionsExtendedMaxDuration payload flags and reads, plus single-line consumers of the discontinued project-functions-extended-max-duration-updated event type
- Remove unreferenced enableFunctionsExtendedMaxDuration bindings from flat resourceConfig destructuring patterns (AST track)
Vercel Projects: publicSource dropped from all project response schemas (v1.28.0)
Pack vercel-project-public-source-removal:
- Remove publicSource response reads and ignored request writes from project API consumers; event-payload reads are preserved (that surface survives)
Vercel Marketplace stores: product.agentSkillUrl reshaped into the agentSkills array (v1.28.3)
Pack vercel-store-agent-skill-url-to-agent-skills:
- Rewrite product.agentSkillUrl member reads to agentSkills?.[0] (scalar-to-array successor)
Vercel VCR: single-image fetch path parameter widened from imageId to imageIdOrDigest (v1.28.9)
Pack vercel-vcr-image-id-or-digest-rename:
- getRepositoryImage: rename the imageId option key to imageIdOrDigest inside the call options object (delete call sites keep imageId — that parameter survives)
Vercel Marketplace: experimentation edge-config fields dropped from the single-resource read (v1.28.9) — reads move to the list endpoint
Pack vercel-integration-resource-edge-config-read-move:
- Rewrite single-resource installation reads that consume the removed experimentation fields into a list-endpoint read plus find-by-partnerId
Vercel REST API: the /v1/edge-config resource family renamed to /v1/global-config (v1.28.14)
Pack vercel-edge-config-to-global-config-path-rename:
- Rewrite raw REST paths /v1/edge-config* and .../experimentation/edge-config to their global-config successors (URL builders and route constants)
Preview each migration as one diff
Every pack runs locally as a dry run by default. On the bundled fixture repos each pack rewrites between one and three files, writing a unified patch you can read before anything is applied:
npx mendapi sync # -> one network call: pulls the change feed into a local database npx mendapi scan # -> which files touch the Vercel SDK, with line numbers mendapi fix --migration vercel-project-public-source-removal # -> dry run: changes.patch + fix-report.json, nothing applied mendapi fix --migration vercel-project-public-source-removal --apply # -> applies the reviewed diff
Nothing leaves your machine at any step; zero network calls is the default and only mode. The report records which rule fired where, and every rewritten file carries a syntax-check verdict, so review is a read of the patch, not an audit of the tool.
The diff itself, byte for byte
This is the actual changes.patch the vercel-edge-config-to-global-config-path-rename pack's dry run produces on the bundled fixture — embedded here from the golden regression evidence at page build time, not retyped. Two files change: raw REST paths under the removed /v1/edge-config family are rewritten to their /v1/global-config successors, in plain string routes, in template-literal URL builders like the items, schema, and backups restore paths, and in the marketplace experimentation/edge-config write path. Note what the codemod leaves alone: comment lines that do not carry an affected route (nested item routes under the removed family, marketplace write path under the removed family) survive byte-for-byte, and the surrounding request plumbing — method: 'POST', headers, body serialization — is context, not rewrite. The rule is anchored to the route family, not to any identifier that happens to say edge.
--- a/index.js +++ b/index.js @@ -10,19 +10,19 @@ } async function listConfigs(headers) { - // collection route: /v1/edge-config - return api('/v1/edge-config', { headers }); + // collection route: /v1/global-config + return api('/v1/global-config', { headers }); } async function readItems(configId, headers) { // nested item routes under the removed family - const items = await api(`/v1/edge-config/${configId}/items`, { headers }); - const schema = await api(`/v1/edge-config/${configId}/schema`, { headers }); + const items = await api(`/v1/global-config/${configId}/items`, { headers }); + const schema = await api(`/v1/global-config/${configId}/schema`, { headers }); return { items, schema }; } async function restoreBackup(configId, versionId, headers) { - return api(`/v1/edge-config/${configId}/backups/${versionId}/restore`, { + return api(`/v1/global-config/${configId}/backups/${versionId}/restore`, { method: 'POST', headers, }); --- a/marketplace.js +++ b/marketplace.js @@ -4,7 +4,7 @@ async function pushExperimentationConfig(icId, resourceId, body, headers) { // marketplace write path under the removed family const res = await fetch( - `${BASE}/v1/installations/${icId}/resources/${resourceId}/experimentation/edge-config`, + `${BASE}/v1/installations/${icId}/resources/${resourceId}/experimentation/global-config`, { method: 'PUT', headers, body: JSON.stringify(body) }, ); return res.json();
What the codemods refuse to touch
A migration tool earns trust by what it leaves alone. Reads and writes that are not anchored to the affected API surface stay untouched: the same property name on an unrelated object is never rewritten, and the publicSource pack explicitly preserves event-payload reads because that surface survives the change. Every rule is locked by golden-fixture regression tests with negative controls (fixture lines that must survive byte-for-byte), so a rule that starts over-firing fails our build before it reaches your repo.
One patch release, measured: what v1.28.14 actually removed
The sharpest single data point in the series is the last step. Diffing Vercel's own OpenAPI snapshots between v1.28.13 and v1.28.14 — one patch release — produced 136 raw diff records. Most of that is additive growth: 71 new response properties, 39 new enum values, 12 new endpoints. But 12 endpoints were removed outright in the same release, all of them the /v1/edge-config resource family and its integration read:
- path-removed: /v1/edge-config
- path-removed: /v1/edge-config/{edgeConfigId}
- path-removed: /v1/edge-config/{edgeConfigId}/backups
- path-removed: /v1/edge-config/{edgeConfigId}/backups/{edgeConfigBackupVersionId}
- path-removed: /v1/edge-config/{edgeConfigId}/backups/{edgeConfigBackupVersionId}/restore
- path-removed: /v1/edge-config/{edgeConfigId}/item/{edgeConfigItemKey}
- path-removed: /v1/edge-config/{edgeConfigId}/items
- path-removed: /v1/edge-config/{edgeConfigId}/schema
- path-removed: /v1/edge-config/{edgeConfigId}/token
- path-removed: /v1/edge-config/{edgeConfigId}/token/{token}
- path-removed: /v1/edge-config/{edgeConfigId}/tokens
- path-removed: /v1/installations/{integrationConfigurationId}/resources/{resourceId}/experimentation/edge-config
Every removed endpoint has a successor under /v1/global-config, which is why the rename ships as a deterministic codemod (the vercel-edge-config-to-global-config-path-rename pack above) instead of a judgment call. Each number in this section is read from the archived spec-diff evidence at page build time, so the page cannot drift from the data it cites.
The part no codemod fixes
Honest scope: the packs handle the mechanical rewrites. They do not decide how your product should react to a discontinued analytics dimension or a withdrawn duration flag — those are behavior decisions, not rewrites. The tracked Vercel history counts 98 breaking or deprecation entries, and the platform-behavior ones stay not code-fixable by verdict, because pretending a codemod covers them is how migrations go wrong.
Related
- Vercel API breaking changes: complete tracker — every tracked entry with fixability verdicts.
- cloudflare-typescript v6 to v7 migration — the same codemod discipline applied to a big-major SDK rewrite.
- Migration pack catalog — every deterministic fix mendapi ships.