devAlice
← Windows

Node.js on Windows — Native vs WSL2, which should you use?

Differences between native Windows Node.js and WSL2 Linux Node.js — setup, troubleshooting, and a decision table by project type.

The first question when running Node.js on Windows: "Install in WSL2 Linux, or native on Windows?" Both work, but performance, tooling compatibility, and debugging experience differ dramatically. This guide compares the two environments, gives you a decision rule, and walks through both setups.

I believe the WSL2 path is the right default for most JavaScript/TypeScript projects on Windows. Not because native Windows Node is broken, but rather because the web toolchain — native modules, shell scripts, CI parity — assumes a Linux environment, and WSL2 is what makes that assumption true on a Windows machine. Because CI runs Linux, the fewer translation layers between your local environment and CI, the more trustworthy your local test results.

Audience: developers writing JavaScript/TypeScript on Windows 11. Assumes Windows initial setup is done.

TL;DR

ScenarioPick
Next.js · React · Express · NestJS · Vite · general web backendWSL2 Linux Node
Electron · Tauri desktop appsNative Windows Node
Company CI is LinuxWSL2 Linux Node
Depend on Windows-only native modulesNative Windows Node
Learning / onboardingWSL2 Linux Node (matches Mac guides)

Rule of thumb: production is Linux anyway. Developing in WSL2 matches production. Without a strong counter-reason, go WSL2.

Prerequisites

1. Native vs WSL2 — core differences

Performance

OperationNative NodeWSL2 Node
npm install (1000 packages)25s15s
next dev first compile5s2s
HMR (Hot Module Reload)800ms+300ms
jest 1000 tests25s18s
File watcherStable but slowFast but occasionally drops events

WSL2's ext4 destroys NTFS on the "many small file reads" pattern Node generates. Caveat: mounting Windows files (/mnt/c/...) inside WSL2 is slower than native — see §6.2.

Tooling compatibility

ToolNative WindowsWSL2
node-gyp builds (native modules)Needs Visual Studio Build ToolsStandard build-essential
sharp · canvas · node-sassOften missing prebuilt binariesAlways works
playwrightWorksWorks (Linux browsers)
puppeteerWorksWorks (separate Chrome install)
DockerThrough Docker Desktopdocker CLI directly

Debugging

AspectNativeWSL2
VS Code debuggerWorks out of the boxNeeds Remote-WSL extension
Chrome DevToolslocalhost:3000 directlyAuto-forwarded WSL2 → Windows
Env vars.env works.env works
Path separatorC:\Users\... (backslash)/home/... (forward slash)

2. Decision table

Pick WSL2 if

  • Production is Linux — Vercel / AWS / GCP / Heroku covers 99% of cases
  • Heavy use of node-gyp modulessharp, canvas, @tensorflow/tfjs-node
  • You want a fast dev server + HMR — the biggest day-to-day differentiator
  • CI runs Ubuntu/Alpine — testing on Linux ensures CI passes
  • You collaborate with Mac users — nearly identical (POSIX) environment

Pick native Windows if

  • Building Electron desktop apps — producing Windows binaries
  • Tauri Windows builds — Rust + WebView2
  • Windows-only APIs — COM, Registry access
  • Simple automation scripts — Notepad automation, etc.
  • VS Code Remote-WSL feels too slow on a low-spec machine

Or use both

Common: WSL2 for work, native for side desktop apps. No conflict — separate PATHs.


3.1 Enter WSL Ubuntu

wsl -d Ubuntu-24.04
# Install mise (language version manager)
curl https://mise.run | sh
echo 'eval "$(/home/$USER/.local/bin/mise activate bash)"' >> ~/.bashrc
exec bash
 
# Install Node
mise use --global node@22
node --version    # v22.x

More on mise: mac/dev-toolchain — works the same on Linux.

3.3 Or nvm

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
exec bash
nvm install --lts
nvm use --lts

3.4 Or apt (system package)

curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs

⚠️ apt makes version switching awkward and depends on sudo. Prefer mise or nvm.

3.5 pnpm · yarn

npm install -g pnpm yarn

3.6 Verify

node --version    # v22.x
npm --version
pnpm --version
which node        # ~/.local/share/mise/installs/node/22/bin/node (with mise)

4. WSL2 + VS Code integration

4.1 Remote-WSL extension

VS Code Extensions:

  • WSL (ms-vscode-remote.remote-wsl)

After installing, command palette → WSL: Connect to WSL → pick Ubuntu.

Or, from inside WSL Ubuntu:

cd ~/projects/myapp
code .

VS Code opens on Windows; the backend runs in WSL2.

4.2 Effect

  • node / npm / pnpm execute inside WSL2
  • Files live on WSL2's ext4 (~/projects/myapp)
  • The terminal is WSL2 bash
  • Debugger and extensions run on the WSL2 side

Don't work under the Windows filesystem (C:\) — performance plummets.


5. Native Windows Node.js setup

5.1 winget install

# Latest LTS
winget install OpenJS.NodeJS.LTS
 
node --version    # v22.x

5.2 Or fnm (version manager)

winget install Schniz.fnm
 
# Auto-activate from PowerShell profile
notepad $PROFILE
# Add:
# fnm env --use-on-cd | Out-String | Invoke-Expression

Auto-switches based on .nvmrc / package.json#engines.node.

5.3 Or NVM for Windows

winget install CoreyButler.NVMforWindows
 
nvm install lts
nvm use lts

nvm-windows is a separate project from Unix nvm. Commands are similar.

5.4 Build tools (for compiling native modules)

To build sharp / canvas / bcrypt etc.:

winget install Microsoft.VisualStudio.2022.BuildTools

Or:

npm install -g windows-build-tools  # deprecated — prefer Visual Studio Build Tools

5.5 Verify

node --version
npm --version
pnpm --version

6. Frequent pitfalls

6.1 Both installed → confused which one runs

WSL2 has its own node, Windows has its own node — different versions per terminal. Fix:

  • Inside WSL bash, WSL's Node (which node~/.local/share/mise/...)
  • In PowerShell, native Node (Get-Command nodeC:\Program Files\nodejs\...)
  • Pin the VS Code terminal default (terminal.integrated.defaultProfile.windows: Ubuntu (WSL))

6.2 Windows-filesystem mount is slow

Working from /mnt/c/Users/me/projects/myapp in WSL2 → 5-10× slower I/O.

# ❌ Slow
cd /mnt/c/Users/me/projects/myapp
npm install
 
# ✅ Fast
cp -r /mnt/c/Users/me/projects/myapp ~/projects/
cd ~/projects/myapp
npm install

Migrate any existing Windows-folder project to WSL2 once.

6.3 Line endings (CRLF vs LF)

Edit in WSL2 → commit → open the same file in Windows → CRLF gets injected. Fix:

git config --global core.autocrlf input    # inside WSL2

More: /multi-os/git-line-endings.

6.4 EACCES on npm install -g

# Inside WSL2 — change the npm prefix
mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
echo 'export PATH=$HOME/.npm-global/bin:$PATH' >> ~/.bashrc
exec bash

On native Windows, use an admin PowerShell.


7. Verify (whichever environment you picked)

# WSL2 or PowerShell
node --version
npm --version
 
# Create package.json + install dep + run
mkdir hello-node && cd hello-node
npm init -y
npm install express
cat > index.js <<'EOF'
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('hello'));
app.listen(3000, () => console.log('http://localhost:3000'));
EOF
node index.js
# → browser localhost:3000 → "hello"

If that works, setup is done.


8. Troubleshooting

Cannot find module 'sharp' (or another native module)

  • Native Windows: install Visual Studio Build Tools, then npm rebuild
  • WSL2: sudo apt install build-essential, then npm rebuild

Dev server unreachable from Windows browser (WSL2)

  • Make the app bind 0.0.0.0 or :: (e.g., next dev -H 0.0.0.0)
  • Windows 11 22H2+ auto-forwards; earlier versions reach via localhost

pnpm command not found (PATH issue)

  • Find where npm install -g puts binaries and add it to PATH
  • Native: npm config get prefix<prefix>\bin
  • WSL2: $HOME/.npm-global/bin or the mise path

node-gyp build "Python not found"

  • Native Windows: winget install Python.Python.3.12
  • WSL2: sudo apt install python3 python3-pip

Can I build Electron from WSL2?

Possible, but complex. Electron's packaging produces host-OS artifacts (.exe / .app / .deb), so Windows builds belong on native Windows and Linux builds belong in WSL2. For Electron, prefer native Windows.

Next.js dev server keeps restarting (WSL2)

Don't work under /mnt/c/ (§6.2). Also use turbopack or set webpackDevMiddleware options in next.config.ts.


9. What's next


References

Changelog

  • 2026-05-16: First draft. WSL vs Native comparison + both setup paths + four pitfalls + six troubleshooting cases.

Keep reading