This message is a fossil from an older JavaScript runtime: SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode. If it appears while installing a modern npm package, the package is usually not the surprising part—the node executable that parsed it is.

Why this error happened

let, const, and class are standard JavaScript. Early Node.js releases shipped V8 versions that exposed some block-scoped syntax only under strict-mode or feature constraints. A newer dependency could therefore fail during parsing—before its code executed at all. Every Node.js line old enough to produce the original error is now end-of-life.

1. Identify the runtime that produced the stack trace

Terminalbash
node --version
npm --version
command -v node
node -p "process.execPath"
node -p "process.versions"

What these checks establish

  • node --version identifies the runtime major; compare it with the project’s supported range and Node.js release-status page.

  • npm --version records the package-manager version but does not prove npm owns the Node executable.

  • command -v node shows the shell resolution, while process.execPath reports the executable from inside the running process.

  • process.versions records the embedded V8 and other component versions—valuable evidence when an IDE, CI runner, or service behaves differently from your terminal.

2. Check what the project actually requires

projectbash
node -p "require('./package.json').engines || {}"
npm config get prefix
npm config get userconfig

Do not upgrade by guesswork

  • A package.json engines.node range communicates the versions the project or package expects; CI and deployment configuration may narrow it further.

  • No engines entry is not proof that every Node release works. Read the project’s support policy and lockfile history.

  • The npm prefix and user-config path expose mixed root/user installations and unexpected global state.

  • Select a maintained Node.js line that satisfies the application and its dependencies. Production guidance favors Active or Maintenance LTS, not simply the numerically newest release.

3. Switch to a supported runtime with a version manager

Use an installation method listed on the official Node.js download page or one approved by your organization. For a shell already configured with nvm, the following selects the current LTS line without hard-coding a version that will age.

Terminalbash
nvm install --lts
nvm use --lts
node --version
node -p "process.execPath"

Risk level: caution. Review the command before running it.

What changes—and what does not

  • nvm install --lts downloads the current LTS release through the configured nvm installation; review nvm’s provenance and organizational policy first.

  • nvm use --lts changes the active runtime for the current shell. New shells need the project’s version/default configuration.

  • The final checks prove the new runtime is selected instead of an older /usr/bin/node or /usr/local/bin/node.

  • A runtime switch does not automatically rebuild native add-ons or make an unsupported application compatible; continue with the project’s tests.

4. Reinstall project dependencies predictably

projectbash
npm ci
npm test

Risk level: caution. Review the command before running it.

Why a clean dependency install matters

  • npm ci requires a compatible lockfile, removes the existing node_modules, and installs the locked dependency graph without rewriting it.

  • Because it deletes the dependency directory, preserve any illicit manual edits before running it—then replace those edits with a patch or declared dependency.

  • Native add-ons may need rebuilding for the new Node ABI; a clean install handles their normal lifecycle scripts.

  • npm test is only as meaningful as the project’s configured suite. Also run build, lint, integration, and smoke checks required by the repository.

5. Prove block-scoped syntax parses

Terminalbash
node -e "{ const answer = 42; let label = 'ready'; console.log(label, answer); }"

What this tiny test isolates

  • node -e asks the selected runtime to parse and execute the supplied script without involving npm or the application.

  • const and let are scoped to the braces; a supported modern runtime accepts them in ordinary CommonJS scripts.

  • Successful output proves syntax support, not application compatibility. If the real command still fails, compare executable paths and reproduce through the same service, IDE, or CI environment.

  • If this command fails on a purportedly modern release, the version/path evidence is inconsistent; resolve that before changing source code.

Strict mode is not the modern fix

Adding "use strict" could alter parsing behavior in some ancient engines, which explains the wording of the historical error. It is not a responsible fix today: those runtimes lack years of security patches and ecosystem compatibility. Modern ECMAScript modules are strict by definition, while modern CommonJS code also supports block-scoped declarations without a strict-mode directive.

Do not confuse this with CommonJS/ESM errors

  • Cannot use import statement outside a module concerns module format, not support for let or const.

  • Node recognizes ESM through markers such as .mjs or a nearest package.json with "type": "module"; .cjs explicitly selects CommonJS.

  • Converting formats can affect require, exports, module.exports, __filename, resolution, and test/build tooling. Do not add "type": "module" only to silence one message.

  • Match the dependency’s documented import style and the project’s chosen module system, then test the entire dependency graph.

When the terminal works but the build still fails

  • CI uses an old image: print node --version and process.execPath inside the failing job, then update the runtime action/image deliberately.

  • IDE task uses another PATH: restart the IDE and configure its runtime explicitly. Integrated terminals and task runners can resolve differently.

  • systemd/cron uses system Node: inspect the service unit or scheduled environment; interactive version-manager initialization is usually absent.

  • `sudo npm` finds another Node: stop mixing root and user package trees. Install dependencies within the project as its owning user.

  • Docker image is stale: pin a maintained official base-image line, rebuild without stale cache where appropriate, and test native dependencies.

  • A transitive dependency raised its minimum Node version: inspect the lockfile/change log and either migrate the runtime or select a supported dependency version—never edit installed package syntax in place.

Prevent the error from returning

  • Declare the supported Node range in package.json and pin the team’s runtime with the version-manager/container mechanism it actually uses.

  • Print runtime versions early in CI logs and fail fast when they fall outside policy.

  • Commit the lockfile for applications and use reproducible installs.

  • Track Node.js release status; move production systems before their release line reaches end-of-life.

  • Inventory background services, IDEs, containers, and deployment images—not only the developer’s interactive shell.

Primary references