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:
Path separators.C:\Users\georg\project (Windows) vs /Users/georg/project (Mac/Linux). Most modern tools handle both, but legacy ones don’t.
Line endings. Windows files default to \r\n (CRLF); Unix files use \n (LF). Git, Node, and many tools care about this.
PowerShell ≠Bash. The default Windows shell has different syntax for everything from variables to pipes to chaining.
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.
rm -rf doesn’t work in cmd or default PowerShell. Cross-platform npm scripts that use it break on Windows.
Antivirus scanning slows everything. Windows Defender (and other AV) scans node_modules aggressively. Builds take 2-3Ă— longer than on Mac.
Symlinks need admin or Developer Mode. Some tools (pnpm, monorepo workspaces) use symlinks. Windows blocks them by default.
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:
Tutorials assume Mac/Linux. A tutorial that says export FOO=bar won’t work in default PowerShell. Knowing the translation saves frustration.
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.
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 24winget install Microsoft.VisualStudioCodewinget install Microsoft.WindowsTerminalwinget 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.xnpm --version # 10.x or 11.xcode --version # 1.95+
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.
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
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.
7. (Optional but recommended) Install WSL2
wsl --install# Rebootwsl --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 when
Use WSL when
You’re new to web dev
You’re comfortable with terminals
Your team uses Windows tooling
You want maximum Linux-parity
You want one filesystem to manage
You have lots of cross-platform tutorials to follow
Visual Studio Code is your primary editor
The 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:
Concept
Bash / Zsh
PowerShell
Set env var
export FOO=bar
$env:FOO = "bar"
Read env var
$FOO or ${FOO}
$env:FOO
Chain commands
cmd1 && cmd2 (run cmd2 if cmd1 succeeds)
cmd1; if ($?) { cmd2 }
List files
ls
ls (aliased) or Get-ChildItem
Print
echo "hello"
echo "hello" (works) or Write-Host
Test path
[ -d /path ]
Test-Path C:\path
Backtick
command substitution
line 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.
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.
Symlinks need permissions
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 foldercd C:\Users\georg\projects# 3. Create the projectnpx create-next-app@latest my-appcd 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 Codecode .# 7. Run dev servernpm 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.
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.