Release strategy for a JS library (semver, changesets)
Semver strictly: breaking → major, additive → minor, fixes → patch. Use **Changesets** so every contributor's PR articulates the impact; CI consumes changesets to bump versions + write changelog. Pre-release channels (`next`, `canary`) for risky changes. Two-week stabilization. Maintain prior major branches with security patches. Codemods for breaking migrations.
Versioning
| Change | Bump |
|---|---|
| Bug fix, no API surface change | Patch |
| New API; existing API intact | Minor |
| Removed/renamed/narrowed API or behavior | Major |
| Type narrowing in return / type widening in required args | Major |
Changesets workflow
npx changeset # contributor adds a changeset file
# .changeset/foo-bar.md:
# ---
# "@org/lib": minor
# ---
# Added Button asChild prop.CI runs changeset version to:
- Bump
package.json. - Append to
CHANGELOG.md. - Bump dependent packages in monorepos.
Then changeset publish ships to npm.
Pre-release channels
npm publish --tag nextConsumers opt in with npm i @org/lib@next. Pin canary changes for early adopters; stabilize for ~2 weeks before promoting to latest.
Release branches
For widely-used libraries, maintain prior majors:
main ← v3.x active
v2.x branch ← v2.x security patches
v1.x branch ← end-of-lifePublish with npm publish --tag v2 for non-current majors.
Breaking change discipline
For major bumps:
- Deprecate first —
@deprecatedJSDoc +console.warnfor one minor cycle. - Document in MIGRATING.md with before/after examples.
- Codemod for non-trivial migrations (jscodeshift / ts-morph).
- Coexist — provide a compat layer when realistic.
Automated checks
- Bundle size budget (
size-limit) — regression fails CI. - Type tests (
tsd,expect-type) — type changes can't slip in. - Visual regression (if a UI library — Chromatic / Playwright).
- A11y (axe in stories).
- Snapshot of API surface (api-extractor) — public API changes require explicit review.
Release rhythm
- Patch: as often as bugs ship. Same day for security.
- Minor: every 2–4 weeks for an active library.
- Major: rare; usually 6–18 months apart. Big migration cost on consumers.
Pre-1.0
Semver-wise, < 1.0 means anything goes. Some libraries abuse this (every change a "minor"); strict shops still respect contract within 0.x.
Anti-patterns
- "It's just a small breaking change" landed in a minor.
- No changelog → consumers don't know what changed.
- Releasing on Friday afternoon.
- No pre-release channel → big risk every major.
- Manual version bumps → human error in monorepos.
Interview framing
"Strict semver via Changesets — every contributor's PR drops a changeset file articulating the impact, CI consumes changesets to bump versions and write the changelog. Pre-release channel (@next) for risky changes, two-week stabilization before promotion. Maintain prior major branches with security patches. For major bumps: deprecate APIs for one minor before removing them, ship codemods for non-trivial migrations, write a MIGRATING.md. CI gates: bundle size, type tests, public API surface diff, visual regression for UI libraries. Avoid Friday releases."
Follow-up questions
- •Why Changesets over a single 'maintainer bumps version' workflow?
- •How do you handle a regression that ships to latest?
- •What's an API surface snapshot?
Common mistakes
- •Breaking change in a minor.
- •No changelog.
- •No deprecation cycle.
- •Friday releases.
Performance considerations
- •CI cost of release pipeline. Bundle/test gates pay back in regressions caught.
Edge cases
- •Monorepo: dependent packages bump correctly.
- •0.x semver looseness.
- •Hotfix for a security bug across all maintained majors.
Real-world examples
- •Next.js release cadence, React's deprecation+migration cycles, MUI's Changesets-based workflow.