5 min read
I bundled pathe 2.0.3 for the browser at 2,268 B minified / 1,091 B gzip. node:path will not resolve in a browser esbuild build at all. On the same box, a mixed join/normalize/resolve workload ran about 38,000 ops/s with pathe versus about 114,000 ops/s with node:path — roughly 3× slower for the portable option.
Short answer: keep node:path in Node-only code. Add pathe when the same path helpers must run in the browser, in isomorphic Vite plugins, or when you need Windows backslashes normalized to / on every OS. No affiliate links in this post.
| Build | Minified | gzip -9 | Browser esbuild |
|---|---|---|---|
| pathe 2.0.3 (named exports I used) | 2,268 B | 1,091 B | ok |
| node:path (platform browser) | — | — | resolve error |
| node:path (platform node, external) | 266 B stub | 162 B | N/A (Node only) |
The stub row is esbuild leaving the built-in external under platform node; it is not a browser polyfill. For front-end budgets, size-limit vs bundlesize will see about 1.1 KB. Vite-era tooling often already depends on pathe transitively — check with knip vs depcheck before adding another copy.
| Workload (8 samples × join/normalize/basename/dirname/extname/resolve, median of 9) | pathe | node:path | node:path/posix |
|---|---|---|---|
| run 1 (pathe first) | 37,231 ops/s | 113,894 ops/s | 114,895 ops/s |
| run 2 (order reversed) | 38,561 ops/s | 114,507 ops/s | 113,381 ops/s |
Yes, about 3× on this Linux box. That gap rarely matters for a few path joins per request or per plugin hook. It would matter in a hot loop over tens of thousands of file paths — the kind of scan tsx vs ts-node or a bundler entry walk might do on the server, where node:path stays free and faster.
| Input | pathe | node:path (Linux) |
|---|---|---|
| join("C:\Users\a", "src\main.ts") | C:/Users/a/src/main.ts | C:\Users\a/src\main.ts |
| normalize("foo\bar/baz/../qux") | foo/bar/qux | foo\bar/qux |
| isAbsolute("C:\a\b") | true | false |
| isAbsolute("/a/b") | true | true |
| sep | / | / |
| relative("/a/b/c", "/a/b/d") | ../d | ../d |
pathe is a POSIX-slash path helper that still understands Windows prefixes. That is why Vite plugins and unjs tools pick it for code that may run under browser esbuild or on a mixed CI matrix. Pure Node services on Linux can stay on node:path and avoid the 1.1 KB. If your toolchain question is broader, see Vite vs Parcel and esbuild-register vs jiti.
| Situation | My pick |
|---|---|
| Node-only scripts and servers on one OS | node:path |
| Shared Vite plugin / isomorphic utils | pathe |
| Browser bundle that must join paths | pathe |
| Hot loop over huge file lists on the server | node:path |
| Need Windows paths correct while developing on Linux | pathe |
Bottom line: pathe costs about 1.1 KB gzip and ran about 3× slower than node:path here, in exchange for browser builds and consistent / slashes across OS strings. Who should not add it: a pure Node CLI that never leaves the server and never sees backslash inputs. Package-manager and runtime choices around that CLI are covered in pnpm vs npm vs bun.
How this was made: I installed pathe on the ShopperCove box, measured esbuild minify+gzip for browser and node platforms, timed the mixed path workload in two order-reversed runs against node:path and node:path/posix, compared slash and isAbsolute behavior on Windows-style strings, and saved the JSON results. The write-up was drafted with AI help and checked against that output.
Lab evidence
Hands-on on ShopperCove box 6 Oct 2026 ~01:48 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 22.20.0). pathe 2.0.3 vs built-in node:path and node:path/posix, esbuild 0.28.2 minify + gzip -9. Fixture /workspace/bench-pathe-node-path/. Size: pathe 2,268/1,091 B browser-ok; node:path browser build errors with unresolved built-in; platform node stub 266/162 B. Speed (8 sample tuples × join/normalize/basename/dirname/extname/resolve, 20k iters, 9-round median, order-reversed): pathe 37,231 / 38,561 ops/s; node:path 113,894 / 114,507; posix 114,895 / 113,381. Behavior (res-behavior.json): pathe join/normalize turn Windows separators into / and isAbsolute("C:\a\b") is true; node:path on Linux keeps backslashes and returns false. Raw: res-bundle.json, res-speed-run1.json, res-speed-run2.json, res-behavior.json. Not tested: Windows host, Bun/Deno, UNC, per-export tree-shake beyond the imported set. No affiliate.