Compatibility Maintenance
Keep your software working with the ecosystem (Node versions, OS, browsers, deps) it claims to support.
Why
Your software doesn't run in a vacuum — it runs on a Node version, an OS, a browser, alongside other dependencies, and each of those evolves. A compatibility matrix that was green at release goes yellow as Node 18 hits EOL, then red as a transitive dep drops support for your Node version. Maintaining compatibility is the active process of testing against the matrix, upgrading before versions EOL, and documenting the supported ranges. Without it, users discover incompatibility at install time ("doesn't work on Node 14") or worse, at runtime in production. A maintained compatibility matrix is also a sales tool — enterprise users check it before adopting, and a green matrix across 5 Node versions + 3 OSes is a trust signal.
When
Continuously — test against the matrix in CI. Revisit when a dependency or runtime releases a new major or hits EOL.
How
1. Define the compatibility matrix: Node versions x OS x browser (for libs) x key deps 2. Set up a matrix CI job (GitHub Actions matrix) that runs tests on every supported combination 3. When a runtime hits EOL (e.g., Node 16): drop it from the matrix, bump the engines field, announce 4. When a runtime adds a new version (e.g., Node 22): add it to the matrix as "experimental", promote when green 5. When a dep drops support for your Node range: either upgrade your Node range or pin the dep 6. Document the matrix in a SUPPORTED.md table and in package.json engines 7. Test edge cases: the oldest supported Node + oldest OS + oldest dep — the floor combination
Example
# .github/workflows/matrix.yml — compatibility matrix
strategy:
matrix:
node: [18, 20, 22] # supported Node versions
os: [ubuntu-latest, macos-latest, windows-latest]
exclude:
- node: 22 # experimental, not all OS green yet
os: windows-latest
steps:
- uses: actions/setup-node@v4
with: { node-version: ${{ matrix.node }} }
- run: npm ci && npm test
# package.json engines (documented contract)
"engines": { "node": ">=18.0.0 <23.0.0" }
# SUPPORTED.md
| Node | Ubuntu | macOS | Windows | Status |
|------|--------|-------|---------|--------|
| 18 | green | green | green | LTS |
| 20 | green | green | green | LTS |
| 22 | green | green | n/a | current|
| 16 | EOL | EOL | EOL | dropped|
# When Node 16 EOLs (drop from matrix)
# - remove 16 from matrix.node
# - bump engines to ">=18.0.0"
# - changelog: "BREAKING: drop Node 16 support (EOL). Min Node 18."Common Problems
- ⚠Testing only on the latest Node + Linux — Windows and macOS users hit path/encoding bugs that CI never caught.
- ⚠No engines field — users install on Node 14 and get cryptic runtime errors instead of a clear "unsupported" at install.
- ⚠Keeping EOL versions in the matrix to be "nice" — CI runs 50% longer for versions nobody should use, and a fix is blocked because Node 14 fails.
Best Practices
- ✓Run a matrix CI on every supported Node x OS combination — the matrix is the only honest compatibility contract.
- ✓Set engines in package.json so unsupported Node fails at install with a clear message, not at runtime with a cryptic error.
- ✓Drop EOL versions from the matrix promptly and announce it — supporting them wastes CI and blocks fixes for supported versions.