A Node.js major or LTS upgrade is safer when treated as a controlled change rather than a single version edit. This reusable Node.js upgrade checklist helps you inventory the application, test dependencies and native modules, update CI and deployment runtimes, verify production behavior, and prepare a rollback before the change reaches users.
Overview
Node.js version compatibility depends on more than the node executable. Your application may also rely on a package manager, lockfile format, native dependencies, build tools, operating-system libraries, container images, serverless runtimes, process managers, and platform-specific environment settings.
Before starting, identify the target release and confirm its support status on the official Node.js release documentation. “LTS” is useful shorthand, but your upgrade decision should also account for your hosting provider, framework, dependency support, and the maintenance policy of your own application. Do not assume that the newest runtime is automatically compatible with every package in your dependency graph.
| Area | Record before upgrading | Verify after upgrading |
|---|---|---|
| Runtime | Current Node.js version, target version, operating system | Application starts and reports the intended version |
| Package manager | npm, pnpm, Yarn, or another supported tool and its version | Clean installation succeeds from the committed lockfile |
| Dependencies | Direct, transitive, optional, and native packages | Tests, builds, and production paths work |
| Delivery | CI images, Docker base images, hosting runtime, process manager | Build and deployment use the same intended runtime |
| Operations | Health checks, logs, alerts, rollback method | Monitoring detects failures and rollback is rehearsed or verified |
Keep the upgrade in a focused branch or pull request. Separating runtime work from unrelated feature changes makes failures easier to attribute and gives reviewers a smaller, more useful diff.
Checklist by scenario
1. Before changing the runtime
- Write down the current Node.js version from local development, CI, containers, staging, and production.
- Choose the target version and record why it is being adopted, such as support alignment, security maintenance, framework compatibility, or a planned platform change.
- Search the repository for version declarations in
.nvmrc,.node-version,package.json, CI files, Dockerfiles, infrastructure configuration, and deployment settings. - Record the package manager and version. Check the
packageManagerfield if the project uses one, and document how local and CI environments activate it. - Capture baseline results: installation, linting, type checking, unit tests, integration tests, end-to-end tests, production build, and startup.
- List native or platform-sensitive packages, including database drivers, image-processing libraries, cryptography packages, file watchers, and modules that compile during installation.
2. When dependencies may be affected
- Inspect dependency release notes and compatibility statements for your framework, test runner, bundler, ORM, database client, and native modules.
- Update packages only as far as necessary for runtime compatibility unless a separate dependency refresh has been approved.
- Regenerate the lockfile with the intended package-manager version, then review the lockfile diff for unexpected changes.
- Run a clean install in a disposable environment. Avoid relying only on a warm local
node_modulesdirectory. - Test optional dependencies and platform-specific installation paths if your project supports more than one operating system or CPU architecture.
3. When build scripts or APIs are involved
- Run every documented script, not only the default test command. Include development, production, migration, seed, worker, and scheduled-job commands.
- Review uses of deprecated or changed Node.js APIs, especially filesystem, URL, streams, timers, child-process, module-loading, and cryptography code.
- Check whether the application depends on CommonJS and ECMAScript module interoperability. Test both the build output and the way production starts it.
- Exercise error paths, graceful shutdown, request timeouts, stream handling, and signal processing. Runtime upgrades can expose assumptions that ordinary happy-path tests miss.
- Use API integration tests or an API debugging workflow to compare status codes, headers, serialized payloads, and error responses before and after the change. See API Debugging Tools: A Practical Workflow.
4. When CI or containers are involved
Update every execution environment together. A repository that uses one version in CI and another in production has not completed the upgrade.
name: test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: ["TARGET_VERSION"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- run: npm test
- run: npm run buildReplace TARGET_VERSION with the version your project has approved, and use the action or CI syntax supported by your provider. For containers, update the base image deliberately, rebuild without relying on an old layer, and run the application as the same user and with the same environment assumptions used in deployment.
5. When deploying to production
- Promote the new runtime through development and staging before production.
- Verify database migrations, background workers, queues, scheduled tasks, and one-off scripts separately from the web process.
- Confirm the hosting platform supports the selected runtime and that its build image and execution image are aligned.
- Deploy using a canary, rolling, blue-green, or similarly limited approach when the platform supports it.
- Define the rollback version, artifact, configuration, and database position before deployment. A rollback is incomplete if the old application cannot run against the changed database state.
What to double-check
Dependency and native-module compatibility
Native modules may compile against Node.js and operating-system interfaces. A package can install successfully on one machine but fail in a minimal container or on another architecture. Check installation logs, compiled binaries, build tool availability, and the package's supported runtime range. If a dependency is unmaintained, decide whether to replace it, isolate it, or postpone the runtime upgrade with a documented risk decision.
Module systems and package exports
Review type, main, module, and exports settings in relevant packages. Test command-line scripts and production entry points, not just imported application code. Pay particular attention to dynamic imports, file extensions, deep imports, and tools that load configuration files.
Environment and deployment parity
Run these checks in every important environment:
node --versionreports the expected version.- The package manager version is intentional and reproducible.
- Installation uses the lockfile and fails on an inconsistent lockfile when appropriate.
- Environment variables, certificates, timezone settings, locale behavior, and file permissions remain correct.
- Health checks cover dependencies such as databases, caches, queues, and external APIs.
Observability and rollback
Define success before deployment: startup rate, error rate, latency, queue health, worker completion, resource usage, and key business transactions. Compare these signals with the baseline rather than relying only on a successful process start. Keep the prior artifact available, document the exact rollback command or pipeline action, and decide who can trigger it.
Common mistakes
- Changing only the local version. Update version files, CI, containers, hosting settings, documentation, and developer setup together.
- Combining the upgrade with a large dependency refresh. Keep the change narrow where possible, or separate the work into clearly reviewable stages.
- Testing only unit tests. Build, start, integration, end-to-end, migration, worker, and scheduled-job paths can fail independently.
- Trusting a cached install. Reproduce a clean install from the lockfile in CI or a clean container.
- Ignoring native modules. Review compilation and runtime behavior on every supported platform.
- Assuming a successful build means production is safe. Exercise graceful shutdown, health checks, real integrations, and representative traffic.
- Planning rollback after deployment. Prepare the previous artifact and database strategy before the first production change.
- Using outdated migration advice. Recheck the official Node.js release notes, package documentation, package-manager guidance, and hosting runtime options for the target version.
When to revisit
Use this checklist before every Node.js major or LTS change, after a hosting provider changes its supported runtimes, and during seasonal maintenance or capacity-planning cycles. Revisit it sooner when a critical dependency releases a compatibility update, a native module changes its build requirements, CI images are replaced, or production telemetry shows unexplained runtime-related errors.
Finish each upgrade with a short record: source and target versions, dependency changes, test evidence, deployment date, observed metrics, rollback result, and follow-up items. Store it with the repository or delivery documentation. For the next upgrade, begin by reviewing that record, then repeat this sequence:
- Inventory every runtime and tool.
- Confirm target support and dependency compatibility.
- Test from a clean environment.
- Update CI and deployment artifacts.
- Release gradually with measurable success criteria.
- Verify production and preserve a working rollback path.
This turns a one-time migration into a repeatable developer workflow and gives the team a practical baseline for future Node.js version upgrades.