Major Version Upgrade
Bump a major dependency or framework (e.g. React 17→18, Node 16→18) without melting production.
Why
Major versions carry breaking changes that silently break at runtime even when compile passes — removed APIs, changed default behavior, new required config. Skipping majors compounds debt: the longer you wait, the more breaking changes stack, and a 3-major jump is far riskier than three 1-major jumps. Ecosystem support drops for old majors (security patches, library compat), so staying current is also a security posture. A disciplined major upgrade is a planned, reversible, reviewed event — not a Friday-afternoon surprise.
When
When the current major is within 6-12 months of EOL, or when a feature you need is only in the new major. Schedule in a low-traffic week, never during a release freeze.
How
1. Read the CHANGELOG / migration guide end-to-end; list every breaking change that touches your code 2. Branch from main: git checkout -b upgrade/react-18 3. Bump the version in package.json, run install, capture every typecheck/lint error 4. Codemod first: use official codemods (e.g. npx @codemod/cli) for mechanical renames before manual edits 5. Fix remaining breakages one file at a time, commit each fix separately for bisect-ability 6. Run the full test suite + e2e; add tests for any behavior the changelog says changed 7. Deploy to staging for at least one full business cycle; monitor error rate and key metrics 8. Roll out with feature flags or canary if available; keep the old version warm for instant rollback
Example
# React 17 -> 18 upgrade
git checkout -b upgrade/react-18
npm install react@18 react-dom@18
npx tsc --noEmit # surface type errors
# Codemod: replace ReactDOM.render with createRoot
npx @codemod/cli --plugin react-18-upgrade src/
# Manual: update index.tsx
- import { render } from "react-dom"
+ import { createRoot } from "react-dom/client"
- render(<App/>, document.getElementById("root"))
+ createRoot(document.getElementById("root")!).render(<App/>)
# Verify StrictMode double-invoke doesn't break effects
npm test && npm run e2eCommon Problems
- ⚠Upgrading multiple majors at once (e.g. 16→18) — the breaking changes interact and you can't tell which upgrade caused which failure.
- ⚠Trusting typecheck alone — runtime behavior changes (new StrictMode double-invoke, changed event pooling) pass types but break effects.
- ⚠Forgetting transitive deps: bumping React 18 but leaving @testing-library/react at 12 causes silent test failures.
- ⚠Deploying on a Friday and losing the rollback window — if Monday breaks, you can't tell the upgrade from the weekend's other changes.
Best Practices
- ✓Upgrade one major at a time with a full test+staging cycle between each — three 1-major jumps are cheaper than one 3-major jump.
- ✓Keep a running "upgrade backlog" board: every breaking change from the changelog becomes a ticket, so nothing is missed.
- ✓Use feature flags to ship the upgrade behind a toggle, letting you revert in seconds without a redeploy if runtime issues appear.
- ✓Schedule upgrades in Q1/Q2, never Q4 (holiday freeze) or the week before a major launch — give yourself a rollback runway.