Compatibility and limitations
Supported environments
| Environment | Status |
|---|---|
| Browsers, Web Workers and service workers | Supported, directly or through bundlers like Vite and webpack; call init() first. Tested in CI in Chromium, Firefox and WebKit (Safari's engine), on pages, in dedicated workers and in module service workers. fetch() options such as credentials and cache are passed through |
| Node.js 20, 22 and 24 | Supported and tested in CI; Node.js 20 is the minimum |
| TypeScript | Definitions included; TypeScript 5.2 or newer |
| Upload progress, WebSockets | JavaScript API only, from 2.2.0. WebSockets need a built-in WebSocket: browsers, Node.js 22+, or Node.js 20 with --experimental-websocket. Tested in CI on Node.js 20 (with that flag), 22 and 24 |
| Rust | The same client as a Rust library (usage), on tokio or in your own wasm crates |
| Native Node.js transport | In the repository, not yet in the npm package (no prebuilt binaries); build it with npm run build:native. Tested in CI on Linux with Node.js 20, 22 and 24, and run on macOS (Apple silicon); not tried on Windows |
Limitations
Bundle size: the WebAssembly module adds about 121 KB (brotli) to a browser bundle, plus 10 KB of JS glue.
HTTP/2: browsers negotiate it automatically; both Node.js transports use HTTP/1.1 with keep-alive.
Proxies: in Node.js, use the
proxyoption orNODE_USE_ENV_PROXY=1; a proxy agent installed ashttp.globalAgent/https.globalAgent(for example fromhttps-proxy-agent) works too. In browsers, use the system settings.Native transport: has to be built from source for each platform until it's published. Sequential requests are level with the node:http transport (they were about 6% slower than
fetch()before 2.4.0). It isn't used for streamed responses, requests with progress callbacks, theproxyoption orallowPrivateNetwork: false(see Architecture), and the p99 stall once seen in the independent harness no longer reproduces: p99 4.7 ms at a steady 15,000 requests per second (see Under load).WebSockets use the runtime's own
WebSocket: built into browsers and Node.js 22 and newer, and into Node.js 20 only with--experimental-websocket(without it,websocket()throws aNetworkError). In Node.js it doesn't go throughhttp.globalAgentor the client'sproxyoption; pass an undiciProxyAgentasdispatcherto connect through a proxy:jsimport { ProxyAgent } from 'undici'; // npm install undici const socket = client.websocket('wss://stream.example.com/', { dispatcher: new ProxyAgent(process.env.HTTPS_PROXY) });A V8 flag on Node.js 20 to 23: importing rust-fetch turns off V8's
--turbo-inline-js-wasm-callsfor the process. With it on, V8 before 13 could abort the process ("Fatal error … unreachable code") when it deoptimizedclient.get()during its call into WebAssembly, as bursts of cancelled requests did. Throughput is unchanged; other WebAssembly in the process loses that one optimization too. Node.js 24 and newer are unaffected and keep it. Passing--turbo-inline-js-wasm-calls(or--no-…) on the command line keeps your choice. With 2.5.2 and earlier, turn it off yourself (FAQ).Upload progress in browsers needs
XMLHttpRequest, which makes cross-origin requests preflighted (see Upload progress).
Upgrading to 2.0
2.0 fixes long-standing bugs, and some code may have depended on the old behaviour. Check these points:
- Timeouts are enforced in browsers and Node.js. In 1.x the 30 s default was silently ignored, so slow requests never failed. For long downloads, set
timeoutMshigher, or to0to disable it. WasmClientJSON bodies are plain objects, notMaps: usebody.title, notbody.get('title'). JSON with integers above 2^53 no longer fails the request.- More responses are decoded:
application/*+json(e.g.problem+json) is parsed as JSON instead of returned as bytes, and empty bodies arenull. - Errors are real
Errorobjects.kind,message,statusand the other fields are unchanged, andnameequalskind. Like anyError,messageisn't enumerable, soJSON.stringify(error)leaves it out. - Invalid
headersvalues reject with anInvalidInputError; they used to be dropped silently. Numbers and booleans are converted to strings. - Text is decoded as UTF-8, whatever the
charsetinContent-Type. Invalid bytes become�. - The package uses an
exportsmap. Importrust-fetch,rust-fetch/rust_fetch.jsorrust-fetch/rust_fetch_bg.wasm. - Node.js 20+ is required. In Node.js you no longer need to load the
.wasmfile yourself;init()is optional there.
These changed too, but are unlikely to break anything:
- POST and PATCH aren't retried by default (
retryNonIdempotent: truere-enables it). - Your
Content-Typeis no longer duplicated. fetch_jsonparses JSON whatever theContent-Type.- Invalid URLs, and
TRACE/CONNECT, fail fast with anInvalidInputError.
Rust crate users:
RetryConfighas a new field, so add..Default::default()to struct literals.Method::to_reqwest()returns reqwest 0.13'sMethod.RequestBuilder::header()replaces a default header with the same name instead of adding a second value.
1.x keeps receiving critical fixes on the v1.x branch (npm install rust-fetch@1).