Windows dev environment

Status: 🟩 COMPLETE Last updated: 2026-06-19 Plain-English tagline: The handful of Windows-specific quirks that make web development feel different from a Mac — backslash paths, CRLF line endings, PowerShell vs bash, and when to use WSL — plus the setup that smooths most of them away.


In plain English

Most modern web dev tutorials are written by Mac users, for a Mac/Linux audience. The commands, file paths, and shell assumptions implicitly target Unix-like systems. On Windows, the same instructions sometimes Just Work — and sometimes fail in confusing ways.

Web development on Windows IS fully viable in 2026, and it’s George’s setup. But there are 6-8 specific things that bite Windows users:

  1. Path separators. C:\Users\georg\project (Windows) vs /Users/georg/project (Mac/Linux). Most modern tools handle both, but legacy ones don’t.
  2. Line endings. Windows files default to \r\n (CRLF); Unix files use \n (LF). Git, Node, and many tools care about this.
  3. PowerShell ≠ Bash. The default Windows shell has different syntax for everything from variables to pipes to chaining.
  4. Case-sensitive filesystem. Mac and Windows are case-INSENSITIVE by default; Linux (and Vercel’s build server) are case-SENSITIVE. Button.tsx vs button.tsx works locally and fails on deploy.
  5. rm -rf doesn’t work in cmd or default PowerShell. Cross-platform npm scripts that use it break on Windows.
  6. Antivirus scanning slows everything. Windows Defender (and other AV) scans node_modules aggressively. Builds take 2-3Ă— longer than on Mac.
  7. Symlinks need admin or Developer Mode. Some tools (pnpm, monorepo workspaces) use symlinks. Windows blocks them by default.
  8. The PATH variable is finicky. Adding directories to PATH on Windows is error-prone; spaces in paths break things.

The good news: with the right setup (modern Node, Git, VS Code, Windows Terminal, optionally WSL), most of these are minor papercuts rather than blockers. This entry covers the setup that makes Windows web dev feel close to the Mac/Linux experience.


Why it matters

Three concrete reasons knowing the Windows quirks pays off:

  1. Tutorials assume Mac/Linux. A tutorial that says export FOO=bar won’t work in default PowerShell. Knowing the translation saves frustration.

  2. Vercel runs Linux. Your code may work locally on Windows and fail on Vercel because of a case-sensitivity bug you can’t reproduce. Knowing the difference prevents these.

  3. Long-term ergonomics matter. If you’ll spend 1000+ hours per year coding on Windows, a 20-minute setup that eliminates daily friction (CRLF/LF, slow builds, weird shells) pays back enormously.

The trade-off: setting up Windows for web dev is a one-time afternoon of fiddling. After that, it’s smooth — but the afternoon is real.


The setup that handles 80% of issues

A baseline modern Windows web-dev setup in 2026:

1. Install the prerequisites

# Use winget (Windows Package Manager — ships with Windows 11)
winget install Git.Git                # Git for Windows (includes Git Bash)
winget install OpenJS.NodeJS.LTS      # Node 22 LTS or 24
winget install Microsoft.VisualStudioCode
winget install Microsoft.WindowsTerminal
winget install GitHub.cli              # gh CLI (optional but recommended)

Note: Open a NEW PowerShell window after install so PATH updates take effect.

Verify:

git --version          # git version 2.45+
node --version         # v22.x or v24.x
npm --version          # 10.x or 11.x
code --version         # 1.95+

2. Set Git to use LF line endings

git config --global core.autocrlf input
git config --global core.eol lf
git config --global init.defaultBranch main

core.autocrlf input means: convert CRLF → LF when files are committed, but leave files as-is in your working directory. Combined with .editorconfig (next step), this means everything in git is LF, but you can still edit files in any editor.

3. Add .editorconfig to every project

A .editorconfig at the project root:

root = true
 
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
 
[*.md]
trim_trailing_whitespace = false

Almost every editor (including VS Code) respects this file. It normalizes line endings and indentation across the team — Windows and Mac contributors produce identical files.

4. Enable Developer Mode

Settings → Privacy & Security → For developers → Developer Mode: ON

This enables symlinks (used by pnpm, monorepo tools), Long Path support, and a few other useful permissions. Free, no downside.

5. Set Windows Terminal as default

Windows Terminal (the modern terminal app) supports tabs, PowerShell + Git Bash + WSL in one window, theming, and proper Unicode rendering. The old cmd.exe is for nostalgia.

Settings → System → For developers → “Open terminal here” → Windows Terminal.

In VS Code, set:

"terminal.integrated.defaultProfile.windows": "PowerShell"
// or "Git Bash" if you prefer

6. Add node_modules and .next to Windows Defender exclusions

Settings → Privacy & Security → Windows Security → Virus & threat protection → Manage settings → Exclusions → Add an exclusion → Folder.

Exclude:

  • C:\Users\<you>\AppData\Local\npm-cache
  • Each project’s node_modules (or your projects parent folder)
  • Each project’s .next folder (or just exclude dist, build, etc. globally)

This is the single biggest performance win on Windows. Build times drop dramatically. Especially noticeable on cold builds.

wsl --install
# Reboot
wsl --install -d Ubuntu-22.04        # Or your preferred distro

WSL2 (Windows Subsystem for Linux) gives you a full Linux environment inside Windows. For Node development, it’s nearly identical to native Linux — Vercel’s runtime, same paths, same bash. Many developers do all their coding inside WSL.

When to USE WSL vs native Windows:

Use native Windows whenUse WSL when
You’re new to web devYou’re comfortable with terminals
Your team uses Windows toolingYou want maximum Linux-parity
You want one filesystem to manageYou have lots of cross-platform tutorials to follow
Visual Studio Code is your primary editorThe Linux toolchain matters (e.g., specific Linux-only binaries)

For Bible Quest-style projects (Next.js + Vercel), native Windows works perfectly with the setup above. WSL adds complexity for marginal benefit unless you specifically need Linux-only tools.


PowerShell — the default Windows shell

PowerShell is the default Windows shell. It’s POWERFUL but different from bash/zsh in many subtle ways.

Key syntax differences:

ConceptBash / ZshPowerShell
Set env varexport FOO=bar$env:FOO = "bar"
Read env var$FOO or ${FOO}$env:FOO
Chain commandscmd1 && cmd2 (run cmd2 if cmd1 succeeds)cmd1; if ($?) { cmd2 }
List fileslsls (aliased) or Get-ChildItem
Printecho "hello"echo "hello" (works) or Write-Host
Test path[ -d /path ]Test-Path C:\path
Backtickcommand substitutionline continuation

Most of the time, you can use modern PowerShell exactly like bash for daily commands (cd, ls, node, npm, etc.). The differences only matter when you’re writing scripts or chaining commands.

For details, see PowerShell vs Bash.


Common Windows-on-web-dev gotchas (in depth)

Case sensitivity

Mac/Windows: Button.tsx and button.tsx are the SAME file. Linux (Vercel): they’re DIFFERENT files.

This means an import Button from './button' statement that works on Windows can fail on Vercel: the file is actually Button.tsx, and Linux distinguishes.

The fix: always match imports to actual filenames, case-perfectly. ESLint rules like import/no-unresolved catch this; CI builds (which run on Linux) also catch it. Run npm run build locally before pushing — see Run the production build locally.

CRLF vs LF line endings

Windows defaults to CRLF (\r\n). Most modern tools handle both, but:

  • A .sh script with CRLF endings won’t run on Linux (“bad interpreter”)
  • A .gitattributes file controls per-file behavior; pair with core.autocrlf input (set above)
  • Some build tools (older webpack configs, some bundlers) misbehave with CRLF

The fix: core.autocrlf input + .editorconfig (both above). After that, you can forget about line endings.

rm -rf doesn’t work in PowerShell

Old npm scripts often have:

{
  "scripts": {
    "clean": "rm -rf ./dist"
  }
}

On Windows PowerShell, rm -rf is not the same. (rm is aliased to Remove-Item in PowerShell, but the -rf flags differ.)

The cross-platform fix: install rimraf:

npm install -D rimraf
{
  "scripts": {
    "clean": "rimraf ./dist"
  }
}

Or use npx:

{
  "scripts": {
    "clean": "npx rimraf ./dist"
  }
}

rimraf is implemented in Node, so it works everywhere npm does.

Path separator confusion

Windows uses \; Unix uses /. Most tools accept both in input but emit Windows-style on Windows. This leads to:

  • A file path printed by a tool that has \ won’t paste into a Bash command
  • Some configs (e.g. ESLint glob patterns) need / even on Windows
  • Hardcoded paths in tests can fail when run on a different OS

The fix: use Node’s path.join(), path.resolve(), path.sep instead of hardcoding separators. Most npm packages already do this.

Spaces in usernames or project paths

If your Windows username is “George Smith”, your home folder is C:\Users\George Smith\. Spaces in paths break MANY shell commands.

Mitigations:

  • Always quote paths: cd "C:\Users\George Smith\projects"
  • Avoid spaces in PROJECT folder names (my-project not my project)
  • Some old tools genuinely can’t handle spaces — symlink your projects folder to a no-space path if needed

Long path support

Windows historically limited paths to 260 characters. With Developer Mode on (step 4 above), long paths are allowed. Without it, deeply nested node_modules can break installs.

git config --global core.longpaths true

This tells Git specifically to handle long paths. Modern Node + Windows 11 with Developer Mode usually doesn’t need this, but it’s safe to set.

Antivirus scanning slows builds

Windows Defender (default antivirus) scans every file as it’s read/written. node_modules has hundreds of thousands of small files. Builds take 2-3× longer than on Mac.

The fix is in step 6 above — exclude node_modules, .next, build folders.

After exclusion: builds drop from 60 seconds to 15-20 seconds typically.

Modern package managers (pnpm, bun) create symlinks inside node_modules. Windows requires either admin rights OR Developer Mode (step 4) to create symlinks.

With Developer Mode on, this is invisible. Without it, you get “permission denied” errors during install.


When to use WSL

WSL2 gives you a full Linux environment. For some workflows, this is significantly better:

  • Closer to Vercel/production: WSL is Linux. Vercel is Linux. Code that runs in WSL runs the same on Vercel.
  • Better for Linux-only tools: Some npm packages have native Linux dependencies that struggle on Windows.
  • Faster I/O on WSL filesystem: WSL’s ext4 is faster than NTFS for many file operations.
  • Easier scripting: Bash scripts from tutorials run unmodified.

The cost:

  • Filesystem split: WSL has its own filesystem at \\wsl$\Ubuntu\home\.... Mixing files between Windows and WSL is awkward — slow if you do it.
  • VS Code split: You’d typically install the “WSL” extension and run VS Code “Remote” against the WSL filesystem.
  • One more system to maintain.

For Bible Quest, native Windows + the above setup works great. For projects with Linux-specific dependencies (e.g. native compilation, specific Docker images), WSL is worth it.

A reasonable hybrid: stay native Windows for normal work; use WSL only for specific tasks that demand it.


VS Code integration

VS Code on Windows is identical to Mac in 95% of ways. The 5% differences:

  • Terminal default is PowerShell. Change to Git Bash or WSL via the profile dropdown.
  • Line endings setting matters:
    "files.eol": "\n"
    Forces LF on save, regardless of the OS default.
  • The code CLI is in PATH after install (with the “Add to PATH” checkbox during installation).
  • For WSL development, install the “Remote - WSL” extension and use code . from inside WSL.

A concrete example: a Windows-friendly project setup

For a new Next.js project on Windows:

# 1. Open Windows Terminal
# 2. Navigate to your projects folder
cd C:\Users\georg\projects
 
# 3. Create the project
npx create-next-app@latest my-app
cd my-app
 
# 4. Add .editorconfig (often auto-created by create-next-app; verify)
# If missing, paste the .editorconfig from step 3 above
 
# 5. Set git line endings (one-time, global)
git config --global core.autocrlf input
 
# 6. Open in VS Code
code .
 
# 7. Run dev server
npm run dev

Everything Just Works. The project’s CRLF/LF handling is correct. VS Code is configured (via the global settings + project .editorconfig). PowerShell is the default shell but bash commands like npm run dev work fine.

Push to GitHub:

git add .
git commit -m "Initial commit"
gh repo create my-app --public --source=. --push

Connect to Vercel via the dashboard. Deploy. Live.


Common gotchas

  • Don’t use the old cmd.exe. It’s a 1990s shell. Use PowerShell or Windows Terminal with PowerShell/bash inside.

  • Don’t chmod on Windows. File permissions are a Linux concept. Windows has ACLs (Access Control Lists), which work differently. chmod is a no-op or error.

  • WSL files at \\wsl$\ are slow over the filesystem boundary. Don’t run npm commands from Windows-side cmd over a WSL project; do it from inside WSL.

  • Don’t cd into folders with non-ASCII characters from PowerShell — encoding issues happen. Modern Windows is mostly fixed, but legacy issues persist.

  • NPM cache lives in %APPDATA%\npm-cache. Sometimes gets corrupted. npm cache clean --force fixes it.

  • GUI installers usually default to “Just for me” (user-only) installation. For Node, Git, etc., this is fine. For some tools (like global CLI utilities), “All users” is cleaner.

  • The Windows registry can have stale PATH entries. If a tool stops being found, Get-Command <tool> shows where Windows thinks it is. Compare with where it’s installed.

  • Reboots are sometimes required. Especially after install, env var changes, or Developer Mode toggles. PowerShell shows new PATH after re-opening; reboots clear stuck state.

  • Don’t share folders with WSL inadvertently. A naive cp -r ~/win-project ~/wsl-project between filesystems is slow AND changes permissions in unexpected ways.

  • Defender exclusions matter A LOT. Without them, every npm install is slow. Add them once, forget about it.

  • Windows-specific line ending bugs show up as “phantom changes” in git: a file looks unchanged in the editor but git status shows it as modified. This is CRLF/LF drift. Fix with core.autocrlf input + .gitattributes.

  • .bat and .cmd files in npm scripts trigger Windows-specific behavior. Most modern npm packages use .js shims that work everywhere; some legacy ones don’t.

  • node-gyp errors during install mean some native module needs to compile. Windows requires Visual Studio Build Tools for this. Most modern packages avoid native compilation (using prebuilt binaries), but a few legacy ones still need it. Install via npm install -g windows-build-tools (deprecated; newer alternative: npm config set msvs_version 2019).

  • %USERPROFILE% (PowerShell: $env:USERPROFILE or just ~) is your home folder. It’s where dotfiles like .gitconfig, .npmrc, .bashrc (Git Bash), and the .claude folder live.

  • The PATH variable order matters. If two node.exe files are on PATH, the FIRST one wins. After installing nvm or fnm, ensure they precede the system Node in PATH.

  • winget and chocolatey can install the same package; they don’t see each other’s installs. Pick one package manager for the OS. winget is Microsoft’s, ships with Windows 11. choco is older and more popular among power users.

  • PowerShell execution policy restricts running unsigned scripts. Set-ExecutionPolicy RemoteSigned -Scope CurrentUser is the standard developer setting.

  • Get-ChildItem (or ls) returns objects, not strings. Piping in PowerShell passes structured objects. This is more powerful than bash piping but confuses bash users. ls | Where-Object {$_.Name -like "*.ts"} (PowerShell) vs ls | grep .ts (bash).

  • Don’t symlink across NTFS and ReFS. Rare scenario; symlinks between filesystems sometimes fail.

  • VS Code “Open in Terminal” uses the integrated terminal default. If that’s PowerShell, that’s what opens. Change in settings.

  • npm install from a OneDrive-synced folder is unreliable. OneDrive’s file syncing fights with node_modules churning. Move projects OUT of OneDrive folders — typically to C:\Users\<you>\projects\ or C:\code\.

  • Background sync (OneDrive, Dropbox, iCloud) locks files briefly. Sometimes npm install fails because a sync agent has a lock. Pausing sync during installs is a workaround.

  • AI tools often emit Unix paths. If Claude generates code with /usr/local/... or /home/... paths hardcoded, swap to Windows-relative paths or process.env.USERPROFILE.

  • Git Bash quirks: comes with Git for Windows. Mostly Bash-compatible. Native Windows paths (C:\Users\foo) appear as /c/Users/foo. Most things work; some specific Bash features (advanced parameter expansion) may differ.

  • MSYS path mangling — when calling Windows executables from Git Bash, paths starting with / may get converted. Use MSYS_NO_PATHCONV=1 to disable. Rare but baffling when it bites.

  • Don’t try to use a Mac shortcut on Windows. Cmd → Ctrl in most places. F-keys work the same. Cmd+space (Spotlight) → Win+S (search) is the closest analog.


See also


Sources