How to fix NVM slowing down terminal startup on Mac

Jerameel Delos Reyes
3 min readJan 3, 2022
NVM and Node loaded immediately

As a developer, we usually use terminal because it’s cooler than using the GUI… or maybe because it’s supposed to be faster? But having a slow terminal initialization just because of NVM is just really annoying, fortunately we have a solution.

This is how you normally load NVM…

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion

To make it faster, we simply add --no-use

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" --no-use # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion

That should work, until you realize you need to call nvm use to be able to use Node. This is because the fix only delays the time consuming process that should happen on terminal startup and waits for you to call nvm use to actually finish the job. And take note that you need to do this every time, even if you have previously called nvm use on another window.

A Better Solution

For me, a better solution is to do the NVM loading in the background upon user login, so Node is immediately available when you open a terminal once it is loaded in the background. To do that we need to create a LaunchAgent, this would allow us to call a script on user login.

Note: change myuser to your user directory name

  1. Create a new plist file
    /Users/myuser/Library/LaunchAgents/com.user.loginscript.plist with the following contents…
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.loginscript</string>
<key>ProgramArguments</key>
<array>
<string>/Users/myuser/.scripts/login.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<false/>
<key>LaunchOnlyOnce</key>
<true/>
</dict>
</plist>

2. Create a script to load NVM /Users/myuser/.scripts/login.sh with the following contents…

Note: You might need to change bash to your user actual shell location ex. which bash

#!/usr/local/bin/bash# This is the entry file for login scripts# Init nvm
echo 0 > $HOME/.scripts/nvm.state
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
echo 1 > $HOME/.scripts/nvm.state

3. Load the launch agent by calling the command launchctl load -w ~/Library/LaunchAgents/com.user.loginscript.plist , this would call the script once every time you login.

4. Now we need to modify .bash_profile to check if nvm has been finished loading in the background when the terminal starts, if not do --no-use temporarily. It should look like this…

# Init nvm
export NVM_DIR="$HOME/.nvm"
NVM_STATE=$(cat /Users/myuser/.scripts/nvm.state)
if [ $NVM_STATE = 1 ]; then
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
else
echo 'Warning: NVM not yet loaded'
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" --no-use # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
fi

5. Restart your machine

Conclusion

By having the initial loading process of NVM done in the background, we no longer need to wait for it to load during terminal startup. This allows us to do other things in the terminal without being blocked by NVM loading.

--

--