next-on-windows
js documentation reads like it was written on a Mac, tested on Linux CI, and only occasionally remembers Windows exists. Which, to be fair, is probably close to true.
Next.js documentation reads like it was written on a Mac, tested on Linux CI, and only occasionally remembers Windows exists. Which, to be fair, is probably close to true. And most of the time it doesn't matter. Then you hit symlink permission errors because Windows gates symlink creation behind Developer Mode by default, or your .env file has CRLF line endings from a Windows editor and something downstream chokes on the carriage return, or a build script has a backslash in a path where a forward slash was assumed, and you lose an hour to a problem that has nothing to do with your code and everything to do with the operating system underneath it.
next-on-windows is a diagnostic tool for exactly that category of pain. Run next-on-windows check and it looks for the known offenders: symlink permissions, the LongPathsEnabled registry key that governs whether Windows will even accept file paths past the old 260-character limit, CRLF line endings sitting in .env files, backslash paths hiding in package.json scripts. It tells you which ones are actually a problem on your machine right now. Run fix with the right flags and it'll clean up the CRLF issue or flip the long-paths registry setting for you.
What it checks, and what it doesn't
The honest version of this tool is smaller than the pitch. diagnose(), fixCrlf(), and enableLongPaths() are the three real functions. The CLI wraps them in a check command that prints a colored pass/warn/fail per issue, and a fix command with --crlf and --long-paths flags. enableLongPaths() runs reg add ... /v LongPathsEnabled ... /f directly against the Windows registry, no confirmation prompt first, which I'll admit is a slightly aggressive default for something that mutates a machine-wide setting, even a fairly safe one.
Here's where I have to be straight with myself: the README oversells what's actually built. It talks about automatic webpack path patching and port-lock cleanup as if they're features of the tool. They aren't. They're things I meant to build, wrote a paragraph about because I was already imagining the finished version, and never got back to. The gap between "what the README promises" and "what src/cli.ts actually does" is the widest of any package I've shipped, and it's not a small typo like the function-name mismatches in some of my other tools; it's whole capabilities described that don't exist in the code at all.
I could have quietly trimmed the README to match reality before anyone looked closely. I'm choosing not to, here, because the honest account of this tool is more useful than the flattering one: I wrote the vision first, built the first third of it, and moved on to the next problem before finishing. That's a real pattern in how I work, not just a one-off slip. When something is annoying enough to write about, I sometimes write the whole plan before I've built the whole thing, and the plan sits there in the README looking finished even though the code underneath it isn't.
Why I built it anyway
Windows is a genuinely worse experience for a huge amount of the JS tooling world, and almost nobody documents the specific, mechanical reasons why. It's not that Windows is bad at running Node. It's that a decade of Unix-first assumptions baked into build tools surface as small, cryptic failures that read like your fault when they're actually the platform's. I wanted one command that would tell me, in plain language, which of the known Windows landmines I was standing on, instead of rediscovering each one the hard way, again, on every new machine or every fresh clone.
The checks that are built (symlinks, long paths, CRLF, backslash scripts) are the ones that have actually bitten me. I didn't research a comprehensive list of every possible Windows/Next.js friction point and build toward it systematically. I built the checks for the problems I personally hit, in the order I hit them, and stopped when the tool was useful enough to reach for. That's most of my tooling philosophy in one sentence, honestly: solve the specific thing that's actually in front of you, not the general category it belongs to.
What using it looks like
next-on-windows check in a fresh clone tells you, in about a second, whether you're about to lose time to something environmental before you've even started debugging your actual code. That's the value: not comprehensiveness, just triage. Knowing "this is a known Windows thing, here's the fix" versus staring at a stack trace wondering if you broke something is a meaningfully different debugging experience, even when the underlying fix is a one-line registry edit you could've found on Stack Overflow eventually anyway.
What's next
Fixing the README to match the real feature set is overdue, and I mean that specifically: not softening the language, actually removing the claims about webpack patching and port cleanup until I build them for real, or building them and making the claims true. Both are legitimate paths. What isn't legitimate is leaving the gap open indefinitely, and I know that, which is exactly why I'm naming it here instead of letting it sit quietly in a repo nobody reads closely.
Why symlinks specifically get their own check
Of the four checks that actually work, the symlink one is the one most people don't even know to look for. Next.js, and a good chunk of the wider Node ecosystem underneath it, uses symlinks internally: inside node_modules, in monorepo setups, sometimes in the build cache. Windows requires either Developer Mode or admin privileges to create a symlink at all, a restriction that doesn't exist on macOS or Linux. So a project that builds fine for every Mac-using teammate can fail on a fresh Windows machine with an error that mentions neither "Windows" nor "symlink" anywhere in the message; it just says something about a file operation failing, and you're left guessing whether it's a permissions issue, a corrupted install, or something wrong with your own code.
I hit this exact wall setting up a clean clone of one of my own projects on a machine where I hadn't yet turned on Developer Mode. The error was unhelpful. The actual cause (one registry-adjacent Windows setting, unrelated to anything in package.json) took longer to find than it should have, because nothing in the failure pointed at it. That's the check I built first, before any of the others, because it was the one that had personally cost me the most confused time relative to how trivial the actual fix turned out to be.
What "diagnose" actually returns
diagnose() isn't just a pass/fail. It returns a structured result per check: which issue, how severe, and in most cases a one-line suggested fix, which the CLI then renders with colored symbols so a scan of the terminal output tells you at a glance whether you're clear or not. That structure is also why the library half of the package exists separately from the CLI half: I wanted diagnose() to be something I could call from inside a setup script or a CI step on a self-hosted Windows runner, not just something you run by hand once and forget about. The CLI is the interface I use personally. The library export is there for the version of this tool that runs automatically, unattended, before a build even starts, which I haven't fully wired up anywhere yet, but designed the API to support without a rewrite when I do.
Full stack developer. Founder of Yashveer Labs. I build on Windows every day, and this is the tool that tells me when the platform, not my code, is the problem.
Start a conversation about this.
Whether it's next-on-windows itself or the next system worth building, the lab is reachable.