Use PowerShell New-Item for node_modules Junctions on Windows
When moving node_modules outside a OneDrive-synced project, many examples reach for mklink /J.
That works in simple ASCII paths. But on real Windows machines, project paths often include Desktop, localized folder names, spaces, or non-ASCII characters. In those cases, invoking mklink through cmd.exe from PowerShell can fail or garble the path.
For automation, prefer PowerShell's native cmdlet:
New-Item -ItemType Junction -Path $JunctionPath -Target $TargetPath
Why mklink is awkward
mklink is a cmd.exe built-in command, not a PowerShell cmdlet.
That means this pattern crosses a shell boundary:
cmd.exe /c "mklink /J `"$JunctionPath`" `"$TargetPath`""
Now you have to get quoting, escaping, and encoding right. With non-ASCII paths, that boundary can turn a valid path into a corrupted one. The error may claim the path does not exist even though PowerShell can read it.
Why New-Item is better
New-Item runs inside PowerShell and receives PowerShell strings directly.
New-Item -ItemType Junction -Path $JunctionPath -Target $TargetPath -Force
It avoids the cmd.exe quoting layer and handles Windows Unicode paths more predictably.
A reusable script shape
Use a script that can be run repeatedly:
$ErrorActionPreference = "Stop"
$ProjectRoot = Split-Path -Parent $PSScriptRoot
$JunctionPath = Join-Path $ProjectRoot "node_modules"
$TargetPath = "<external-store>\my-project\node_modules"
New-Item -ItemType Directory -Path $TargetPath -Force | Out-Null
if (Test-Path $JunctionPath) {
$item = Get-Item $JunctionPath -Force
if ($item.LinkType -eq "Junction" -and $item.Target -eq $TargetPath) {
Write-Host "node_modules junction already correct"
exit 0
}
Remove-Item $JunctionPath -Recurse -Force
}
New-Item -ItemType Junction -Path $JunctionPath -Target $TargetPath | Out-Null
Write-Host "node_modules junction created"
The key is idempotency. Running the script twice should be safe.
Add it to package scripts
Use npm pre-scripts so the junction is checked before build commands:
{
"scripts": {
"predev": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\\ensure-node-modules.ps1",
"prebuild": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\\ensure-node-modules.ps1"
}
}
This matters because npm can replace a junction with a real folder during install. The script should catch that before development or build starts.
Summary
For node_modules junctions on Windows, New-Item -ItemType Junction is the automation-friendly default.
Use mklink for manual one-off terminal work if you want, but prefer PowerShell cmdlets in scripts. They reduce quoting problems and handle localized paths more reliably.