ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
Topics
sre111localhost lab102performance70python58javascript47bundle size36benchmarking35node.js35web development21typescript19benchmark16content creation15All topics
About
Loading article…

ShopperCove

Hands-on notes on developer tools, written after the tool has actually been run.

All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove

On this page

  1. What I tested
  2. How much faster is undici on Node 22?
  3. What does undici cost in bundle size?
  4. Do they behave the same?
  5. Which one should you pick?
  6. FAQ
  7. Is undici faster than node-fetch?
  8. Does undici request() follow redirects?
  9. Which Node versions does undici 8 support?
  10. Sources
  11. Related
  1. Blog

undici vs node-fetch: Which Is Faster on Node 22? Tested

5 October 2026·6 min read

Hands-on

Disclosure

This post contains affiliate links. If you buy through them, I may earn a commission at no extra cost to you. I only make firsthand claims about products I have actually used.

On this page
  1. What I tested
  2. How much faster is undici on Node 22?
  3. What does undici cost in bundle size?
  4. Do they behave the same?
  5. Which one should you pick?
  6. FAQ
  7. Is undici faster than node-fetch?
  8. Does undici request() follow redirects?
  9. Which Node versions does undici 8 support?
  10. Sources
  11. Related

undici is much faster than node-fetch on Node 22: in a local keep-alive test, undici 8.11.2's request() handled about 31,000-36,000 requests per second and its fetch() about 13,000, against about 7,200-7,400 for node-fetch 3.3.2. Use undici on Node 22.19 or newer (request() for hot server-to-server calls), and keep node-fetch only for older Node versions, code that needs its Node-stream body, or bundles where its 26 KB gzip size beats undici's 173 KB.

What I tested

  • Machine: 8 vCPU Intel Xeon / 15 GB RAM shared Linux cloud box, tested 6 Oct 2026 about 02:30 IST.
  • Versions: undici 8.11.2, node-fetch 3.3.2 (both the npm latest that day), Node 22.20.0 (whose built-in fetch is undici 6.21.2), esbuild 0.28.2 node ESM minify + gzip -9.
  • Load: a local Node http server returning a 764-byte JSON body. 5,000 GET requests at 50 concurrent, JSON parsed and counted for parity, one warm-up round, median of 7 rounds, two processes with the client order reversed. undici used an Agent with 50 connections; node-fetch used http.Agent with keepAlive and 50 sockets; Node's global fetch used its defaults. Scripts and JSON in /workspace/bench-undici-node-fetch/.
  • Findings: request() 35,626 / 30,934 req/s, undici fetch() 13,321 / 13,039, node-fetch 7,176 / 7,436, global fetch 7,911 / 10,827. Bundle (minified / gzip): undici 553,516 / 173,375 B from 111 input files; node-fetch 92,583 / 26,012 B from 22. On disk: undici about 2.5 MB with no dependencies; node-fetch plus its 5 dependencies about 9.4 MB, most of it web-streams-polyfill.
  • One surprise: neither undici 8's Response nor node-fetch's passed an instanceof check against Node 22's global Response, because the built-in fetch ships its own older undici copy.
  • Not tested: HTTP/2, TLS, proxies (ProxyAgent), large uploads or streaming downloads, retries and interceptors, real network latency, Bun or Deno, and Node 18/20, which undici 8 does not support.

How much faster is undici on Node 22?

5,000 GETs, 50 concurrent (median of 7)Run 1 req/sRun 2 req/sRun 1 / Run 2 ms
undici request()35,62630,934140.3 / 161.6
undici fetch()13,32113,039375.3 / 383.5
Node 22 global fetch (undici 6.21.2)7,91110,827632.0 / 461.8
node-fetch 3.3.27,1767,436696.8 / 672.4

Run 1 went request, fetch, node-fetch, global; run 2 reversed it. request() beat node-fetch by 4.2–5.0×, and undici fetch() beat it by about 1.8×. The global fetch number moved a lot with order, so treat it as a range. On loopback this is mostly client overhead; over a real network, latency shrinks the gap. Why both clients were given keep-alive pools is shown with nginx numbers in HTTP keep-alive vs Connection: close. If you mock these calls in tests, nock vs msw measured how mocking cost differs between fetch and Node's http module.

What does undici cost in bundle size?

Build (esbuild node minify, gzip -9)MinifiedgzipInput files
undici 8.11.2 (fetch, request, Agent)553,516 B173,375 B111
node-fetch 3.3.292,583 B26,012 B22

undici is about 6.7× bigger gzipped when you bundle it. That matters for serverless cold starts or a single-file CLI, and not at all if you rely on Node's built-in fetch and bundle nothing. Track it the same way as in size-limit vs bundlesize. If you want a thin wrapper on top of fetch instead, compare ofetch vs ky and ky vs axios.

Do they behave the same?

Check (local server)node-fetch 3.3.2undici fetch()undici request()
Body typeNode PassThrough streamweb ReadableStreamBodyReadable (Node stream)
404 responsestatus 404, ok false, no throwstatus 404, ok false, no throwstatusCode 404, no throw
302 redirectfollowed, redirected truefollowed, redirected truereturns 302, not followed by default
instanceof global Responsefalsefalsenot a Response
Node support12.20+ (ESM only)22.19+22.19+

The redirect row is the one that bites people moving from fetch to request(): you get the raw 302 and have to follow it yourself or add undici's redirect interceptor. node-fetch's body is a Node stream, so code that pipes res.body into fs streams works as is; undici fetch() gives you a web stream instead. URL building around either client is covered in ufo vs URL, and safe JSON parsing of responses in destr vs superjson.

Which one should you pick?

SituationMy pick
Hot server-to-server calls on Node 22.19+undici request()
Standard fetch API, newer than the built-in copyundici fetch()
Just need fetch on Node 22 with no dependencyNode's global fetch
Stuck on Node 18 or 20, or need a Node-stream bodynode-fetch
Bundled CLI or serverless where size beats speednode-fetch or the global fetch

Bottom line: undici 8.11.2's request() moved about 31,000–36,000 req/s on this box versus about 7,200–7,400 for node-fetch 3.3.2, and even undici's fetch() was about 1.8× faster, at the price of a 173 KB gzip bundle versus 26 KB. Who should not switch: projects pinned below Node 22.19, or code built around node-fetch's Node-stream body and automatic redirects that would need rework for request(). Who should not stay on node-fetch: new Node 22 services, where the global fetch or undici covers the same API without a polyfill.

How this was made: I installed both packages on the ShopperCove box, ran a local keep-alive HTTP server, fired 5,000 requests at 50 concurrent through each client in two order-reversed processes, bundled each client with esbuild, and checked body types, 404 handling and redirects against the same server. The JSON results sit next to the scripts. The write-up was drafted with AI help and checked against that output.

FAQ

Is undici faster than node-fetch?

Yes, in this test. undici request() beat node-fetch by 4.2-5.0x, and undici fetch() beat it by about 1.8x, on 5,000 GETs at 50 concurrent against a local server. Over a real network, latency narrows the gap.

Does undici request() follow redirects?

No. request() returns the raw 302 by default, so you follow it yourself or add undici's redirect interceptor. Both node-fetch and undici fetch() follow redirects.

Which Node versions does undici 8 support?

Node 22.19 or newer. node-fetch 3.3.2 supports Node 12.20+ (ESM only), which is the main reason to keep it on older runtimes.

Sources

  • 01github.com/nodejs/undici
  • 02undici.nodejs.orghttps://undici.nodejs.org/
  • 03github.com/node-fetch/node-fetch
  • 04npmjs.com/package/undici
  • 05npmjs.com/package/node-fetch
  • 06nodejs.org/api/globals.html

Related

  • 01shoppercove.com/blog/tinyexec-vs-execa
undicinode-fetchnode.jshttp clientperformance benchmarkfetch apibundle size

Lab evidence

What I found running this

Hands-on on ShopperCove box 6 Oct 2026 ~02:30 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 22.20.0, built-in fetch = undici 6.21.2). undici 8.11.2 vs node-fetch 3.3.2. Load: local keep-alive http server, 764 B JSON, 5,000 GETs x 50 concurrent, warm-up + 7-round median, order-reversed (req/s): request() 35,626/30,934; undici fetch() 13,321/13,039; node-fetch 7,176/7,436; global fetch 7,911/10,827. esbuild 0.28.2 node minify + gzip -9: 553,516/173,375 B (111 inputs) vs 92,583/26,012 B (22). Disk ~2.5 MB 0 deps vs ~9.4 MB with 5 deps. Behavior: request() does not follow 302 by default; both fetches follow; no client throws on 404; node-fetch body is a Node PassThrough, undici fetch a web ReadableStream. undici 8 needs Node >=22.19. Not tested: HTTP/2, TLS, ProxyAgent, streaming, interceptors, real network, Bun/Deno. No affiliate.

Explore the product

Untested by us, and no traffic result is guaranteed. Check the live offer and refund terms first.

AI Traffic Secrets for AI-era SEO

See AI Traffic Secrets details

Ready Client Sites for prebuilt websites

See Ready Client Sites details

Notes when a lab post goes up

Occasional email for new hands-on reviews. No sequence and no sponsors.

Explore the product

Untested by us, and no traffic result is guaranteed. Check the live offer and refund terms first.

AI Traffic Secrets for AI-era SEO

See AI Traffic Secrets details

Ready Client Sites for prebuilt websites

See Ready Client Sites details

Keep reading

  • tinypool vs piscina: 5.4 KB vs 10 KB Gzip

    Hands-on6 Oct 20264 min read

  • Picocolors vs Chalk: 1.0 KB vs 3.4 KB Gzip

    Hands-on6 Oct 20264 min read

  • chokidar vs @parcel/watcher: 603 ms vs 61 ms to Ready

    Hands-on6 Oct 20268 min read