Node.js version management is the ability to install and switch between different versions of Node.js on the same system.
The problem: Different projects may require different Node.js versions:
- Project A: Needs Node.js 18
- Project B: Needs Node.js 20
- Project C: Needs Node.js 16
- Single version: Can't use different versions simultaneously
The solution: Version managers like NVM:
- Multiple versions: Install multiple Node.js versions
- Switch versions: Change version per project
- Isolation: Each project uses its version
- Flexibility: Work on different projects easily
Real-world analogy:
- Version management = Multiple toolboxes (different tools for different projects)
- Single version = One toolbox (limited, can't handle all projects)
- NVM = Toolbox organizer (manages multiple toolboxes)
- Result = Work on any project (right tools for each project)
Definition: NVM (Node Version Manager) is a tool that allows you to install and manage multiple Node.js versions.
How NVM works:
- Installs versions: Downloads and installs Node.js versions
- Manages versions: Keeps multiple versions available
- Switches versions: Changes active Node.js version
- Isolates projects: Each project can use different version
If you use Fish shell on Arch and install NVM (Node Version Manager), you'll run into a classic mismatch:
The problem:
1. NVM is a bash script that works by modifying environment variables (mainly PATH) in your current shell.
What this means:
- NVM is bash script: Written for bash shell
- Modifies PATH: Changes environment variables
- Current shell: Only affects shell where command runs
- Bash-specific: Designed for bash, not Fish
How NVM works in bash:
- Run
nvm use 18: NVM script executes - Modifies PATH: Changes PATH environment variable
- Bash inherits: Bash shell gets new PATH
- Node available:
nodecommand works
2. Fish cannot be "mutated" by a subprocess. So if you run nvm use 18 via a bash subprocess, Fish won't automatically inherit the new PATH.
What this means:
- Fish limitation: Fish shell doesn't inherit environment changes from subprocesses
- Bash subprocess: Running NVM in bash doesn't affect Fish
- PATH unchanged: Fish's PATH stays the same
- Node not found:
nodecommand doesn't work in Fish
How it fails in Fish:
- Run
nvm use 18: NVM runs in bash subprocess - Modifies PATH: Changes PATH in bash subprocess
- Fish unaffected: Fish shell doesn't see change
- PATH unchanged: Fish still has old PATH
- Node not found:
nodecommand doesn't work ❌
That's why, in Fish, nvm use <version> can "succeed" but node -v still shows the old version.
Real-world example:
In Fish shell:
$ nvm use 18
Now using node v18.17.0
# NVM says it worked ✅
$ node -v
v16.20.0
# But Fish still shows old version ❌Problem: NVM changed PATH in bash subprocess, but Fish didn't inherit the change.
This folder provides a minimal, package-manager-friendly way to make NVM usable in Fish without fisher or bass.
What this solution does:
- Works with Fish: Makes NVM work in Fish shell
- Minimal setup: Simple, lightweight solution
- No extra tools: Doesn't require fisher or bass plugins
- Package-friendly: Works with Arch's pacman-installed NVM
We split responsibilities into three small pieces:
Why split into pieces:
- Separation of concerns: Each piece does one thing
- Modularity: Easy to understand and modify
- Maintainability: Simple to maintain
- Flexibility: Can modify pieces independently
1. config.fish: initializes Fish for NVM and (optionally) auto-loads the default Node version.
What it does:
- Initialization: Sets up Fish for NVM
- Environment: Exports NVM directory
- Auto-load: Loads default Node version on startup
- Startup: Runs when Fish shell starts
Why it's needed:
- Setup: Configures Fish for NVM
- Convenience: Auto-loads Node so it's ready
- Integration: Makes NVM work with Fish
2. nvm.fish: a tiny nvm function that runs NVM in bash.
What it does:
- Wrapper function: Creates
nvmcommand in Fish - Bash execution: Runs NVM commands in bash
- Command forwarding: Passes commands to real NVM
- Compatibility: Makes NVM commands work in Fish
Why it's needed:
- Command access: Use
nvmcommands in Fish - Bash compatibility: Runs NVM in bash (where it works)
- Transparency: Works like NVM in bash
3. nvm-load.fish: a loader that syncs the relevant environment back into Fish (so node is on Fish's PATH).
What it does:
- Environment sync: Gets PATH from bash/NVM
- Updates Fish: Sets PATH in Fish shell
- Makes Node available: Puts
nodeon Fish's PATH - Synchronization: Keeps Fish in sync with NVM
Why it's needed:
- PATH sync: Fish needs updated PATH
- Node availability: Makes
nodecommand work - Bridge: Connects NVM (bash) to Fish
Real-world analogy:
- config.fish = Setup crew (prepares workspace)
- nvm.fish = Translator (translates Fish commands to bash)
- nvm-load.fish = Synchronizer (syncs results back to Fish)
- Result = NVM works in Fish (all pieces work together)
This is your Fish startup file (usually ~/.config/fish/config.fish).
It does two important things:
- Exports
NVM_DIR(needed by NVM tooling). - Runs
nvm-load(if available) so your Fish session starts with a workingnodeonPATH.
Notes:
- This repo’s
config.fishcurrently sources a CachyOS shared config. If you don’t have it, remove/comment that line and keep the NVM bits.
Defines the Fish function nvm:
- It forwards your command to bash and runs the real NVM:
bash -ic "source /usr/share/nvm/init-nvm.sh; nvm $argv"
This makes commands like nvm list, nvm install, nvm alias, etc. available in Fish.
Defines nvm-load, which is the key workaround:
- It runs a bash command that sources NVM, selects the default node (
nvm use default), then prints the environment (env). - It parses that output and updates Fish’s environment (especially
PATH, plusNVM_DIR/NVM_BIN).
This is what makes node, npm, and friends actually work in Fish.
Fish can’t receive environment changes from a child process, so nvm use alone won’t update the current Fish session’s PATH.
Use a default Node alias and let nvm-load bring it into Fish:
nvm-load
node -vWhen you change versions, run nvm-load after:
nvm use 18
nvm-load
node -vSee the detailed guide:
node_started/nvm-fish-readme.md
type -q nvm; and echo "nvm function exists"
type -q nvm-load; and echo "nvm-load function exists"
nvm-load
which node
node -v