Version your API from day one (URL prefix, header, or query param). Without versioning, any breaking change forces all clients to update simultaneously or breaks them without warning.
Without API versioning, every change is either backward-compatible or a breaking change that affects all consumers simultaneously. Adding a required field breaks old clients. Removing a field breaks clients that depend on it. Renaming a field breaks everyone. Versioning lets you evolve the API while giving consumers time to migrate, preventing the "deploy and pray" pattern that causes widespread outages.
BeforeMerge scans your pull requests against this rule and 3+ others. Get actionable feedback before code ships.
APIs are contracts. Once a client integrates with your API, they depend on the exact shape of requests and responses. Without versioning, you face a painful choice with every change:
Versioning solves this by letting you run old and new versions in parallel. Clients migrate on their own schedule. You deprecate old versions with advance notice. Breaking changes are contained to new versions.
Version your API from the first release. The three common strategies:
/api/v1/users, /api/v2/usersAccept: application/vnd.myapp.v2+json/api/users?version=2URL prefix is the simplest, most visible, and easiest to route. Use it unless you have a specific reason not to.
// BAD: no versioning — /api/users
// Changing the response shape breaks all clients
export async function GET() {
const users = await db.user.findMany();
return Response.json(
// If you need to change this shape, every client breaks
users.map((u) => ({
id: u.id,
name: u.name,
email: u.email,
}))
);
}app/
api/
v1/
users/
route.ts # Original shape
v2/
users/
route.ts # New shape — old clients unaffected// app/api/v1/users/route.ts — original format
export async function GET() {
const users = await db.user.findMany();
return Response.json(
users.map((u) => ({ id: u.id, name: u.name, email: u.email }))
);
}
// app/api/v2/users/route.ts — new format with breaking changes
export async function GET() {
const users = await db.user.findMany({
include: { profile: true },
});
return Response.json(
users.map((u) => ({
id: u.id,
displayName: `${u.profile.firstName} ${u.profile.lastName}`,
contactEmail: u.email,
avatarUrl: u.profile.avatarUrl,
}))
);
}Check if API routes include a version prefix:
ls app/api/If routes are directly under app/api/ without a v1/ or version prefix, the API is unversioned.
app/api/users/ to app/api/v1/users/