Прямой ответ
Node-проекты живут на разных мажорных версиях: легаси-приложение на 16, текущий прод на 20 LTS, эксперименты на 22 — и всё это одновременно на одном ноутбуке. Системная установка Node такое не поддерживает, поэтому стандартом стали менеджеры версий: nvm (классика, bash-скрипт), fnm (Rust-перепис nvm, в разы быстрее, кросс-платформенный включая нативный Windows) и Volta (фиксирует версию на проект автоматически, по package.json). Рекомендация 2026: на macOS/Linux — fnm (скорость) или nvm (привычка и совместимость со всем туториалами); на Windows — fnm или Volta (nvm-windows — отдельный проект с другим поведением); для команд, которым важна воспроизводимость «у всех одинаково», — Volta.
Почему версий несколько: механика проблемы
Мажорные версии Node ломают обратную совместимость (обновлённый V8, изменения в API, требование новых инструментов сборки); нативные зависимости (модули с C-кодом) компилируются под конкретную версию и при её смене требуют пересборки. Симптом жизни без менеджера: «обновил Node — половина глобальных пакетов сломалась, а старый проект перестал собираться». Менеджер версий изолирует установки друг от друга: каждая версия живёт в своём каталоге со своим npm и глобальными пакетами; переключение меняет PATH. Аналогия для Python-разработчиков: это nvm-эквивалент связки pyenv/venv из статьи про Python-окружения.
nvm: стандарт, с которого начинают
nvm install 20 # поставить LTS
nvm install 22
nvm use 20 # переключить в текущей оболочке
nvm alias default 20 # версия по умолчанию
Плюсы: нулевая конфигурация проекта, знаком всем, работает везде на Unix. Минусы: медленный старт оболочки (bash-скрипт инициализируется сотни миллисекунд — заметно при частом открытии терминалов), на Windows — только форк nvm-windows с отличающимися командами, автопереключения по проекту нет (вручную nvm use).
fnm: тот же UX, быстрые внутренности
fnm install 20 && fnm use 20
# автопереключение по .nvmrc/.node-version при входе в каталог:
eval "$(fnm env --use-on-cd)"
Команды почти копируют nvm (миграция безболезненна), но fnm написан на Rust: установка и переключение мгновенные, shell-init не тормозит терминал, один бинарник честно работает на Windows/macOS/Linux. Фича, которая меняет привычки: --use-on-cd — при cd в проект с .nvmrc версия переключается сама; ошибка «собрал не той версией» исчезает.
Volta: версия как свойство проекта
Volta идёт дальше: она пиннит инструмент в package.json:
volta pin node@20 # запишет "volta": {"node": "20.x"} в package.json
После этого любой запуск node/npm в каталоге проекта автоматически исполняется на закрепённой версии — без use, без .nvmrc, у каждого члена команды одинаково. Дополнительно Volta фиксирует и версии глобальных CLI-пакетов (volta install typescript — доступён во всех проектах на нужной версии). Цена: свой подход к глобальным пакетам (не совместим с «просто npm -g») и чуть меньше экосистемной привычности.
Практика команды: фиксация и engines
Независимо от менеджера, проект обязан декларировать версию:
.nvmrc/.node-versionс мажорной версией — читают nvm/fnm.enginesвpackage.json:{"node": ">=20 <21"}— npm предупредит при несоответствии (жёсткость — черезengine-strict=trueв.npmrc).- CI на той же мажорной версии, что прод: расхождение «CI на 22, прод на 20» — источник сюрпризов после релиза.
Типовые грабли: глобальные пакеты ставятся под конкретную версию (nvm use 22 && npm i -g pnpm — pnpm появится только в 22); после nvm install новой версии глобальный набор пуст — переустановите или перенесите (nvm reinstall-packages 20). Диск: каждая версия Node — 100–150 МБ плюс свои кэши; пять версий — уже полгигабайта, аудит вместе с node_modules — в статье про место на диске.
Что выбрать: коротко
Одиночный разработчик на macOS/Linux, несколько проектов — fnm (скорость + автопереключение). Команда с требованием идентичности окружений — Volta (пиннинг в package.json). «Как в туториале» и минимум нового — nvm. Windows без WSL — fnm или Volta (оба нативные). Во всех случаях системный Node из установщика лучше удалить, чтобы PATH не двоился — частая причина «nvm use не работает».
И финальный аргумент дисциплины: менеджер версий делает видимой саму проблему версий. Когда проект декларирует Node в файле, а оболочка переключается автоматически, расхождение «собрал не тем Node» перестаёт быть плавающим багом — оно либо исключено, либо диагностируется одной командой. Это редкий случай, когда инструмент окупается не скоростью, а исчезновением класса ошибок.
FAQ
Менеджер версий замедлит сборку? Нет: после переключения вы работаете с обычным Node; накладные расходы есть только у nvm на инициализацию оболочки, fnm/Volta почти мгновенны.
Можно ли держать Node и через brew/apt, и через менеджер? Технически да, практически — источник путаницы с PATH и правами на глобальные пакеты. Выберите один источник; менеджер версий покрывает все сценарии.
Как обновлять LTS-версии?
fnm install 20 (или nvm) подтянет свежий минор 20.x; минорные обновления обратно совместимы в пределах мажорной линии — обновляйте регулярно, это патчи безопасности.
Нужен ли менеджер версий, если проект один? Пока один — можно и без него, но установка занимает две минуты и избавляет от будущей ручной миграции; дешевле завести сразу.
Частные случаи, которые всплывают в работе
Нативные модули при переключении версий: пакеты с C-кодом (bcrypt-подобные, sharp) компилируются под конкретную версию Node ABI; после nvm use 22 они требуют пересборки (npm rebuild). Симптом — ошибки NODE_MODULE_VERSION mismatch; лечение — rebuild или переустановка node_modules.
Docker и версии: в контейнере версия Node задаётся базовым образом (node:20-alpine) и не зависит от менеджера на хосте — это правильно: прод-версия фиксируется в Dockerfile, локальная — в .nvmrc, и они должны совпадать мажорно. Расхождение «локально 22, в образе 18» — источник «у меня работает».
Глобальные CLI и PATH: у каждой версии свой набор глобальных пакетов; если tsc «исчез» после переключения — он стоял под другой версией. Решение — ставить такие инструменты через менеджер, который их шарит (Volta), или через pnpm/npx вместо npm -g.
Обновление LTS в команде: договоритесь о ритме (новый LTS — в течение месяца после стабилизации), зафиксируйте его в CI-матрице — и расхождение версий перестанет быть темой для споров.
Как менеджер версий уживается с Docker? Они не пересекаются: в контейнере версия Node определяется базовым образом, на хосте — менеджером. Важно лишь совпадение мажорных версий локальной и контейнерной — иначе «локально собирается, в образе нет». Фиксируйте обе: .nvmrc для хоста, точный тег в Dockerfile.
Что выбрать для команды с разными ОС? Volta или fnm: оба кросс-платформенные и читают один файл версии проекта. nvm плюс nvm-windows — две разные программы с разным поведением, в смешанных командах это постоянный источник мелких расхождений.
Как быстро проверить, какой Node сейчас активен и откуда он? Команды which node (Linux/macOS) или Get-Command node (PowerShell) показывают путь исполняемого файла: если он ведёт в каталог менеджера — всё правильно; если в системные каталоги — PATH конфликтует с менеджером версий.
Источники: проекты nvm, fnm, документация Volta (ссылки в шапке).