react-native-nitro-fetch

repository·main·Indexed 21 days ago

https://github.com/margelo/react-native-nitro-fetch

A high-performance networking library for React Native that serves as a drop-in replacement for the standard fetch API. Optimized with Cronet on Android and URLSession on iOS, it supports HTTP/1, HTTP/2, HTTP/3 (QUIC), Brotli compression, and disk caching. Key features include native prefetching to reduce initialization latency, workletized data mapping via nitroFetchOnWorklet to avoid blocking the JS thread, and streaming responses with TextDecoder. Requires react-native-nitro-modules and React Native 0.75 or higher.

Tokens
56.9K
Snippets
180
Records
237
Agent score
74%

What's inside react-native-nitro-fetch

  1. Overview of the react-native-nitro-fetch ecosystem

    main

    The react-native-nitro-fetch family provides a high-performance, native-backed networking stack for React Native. It serves as a drop-in replacement for standard browser APIs, leveraging Nitro Modules to move networking logic to native code (iOS URLSession and Android OkHttp/HttpURLConnection).

    Core components include:

    • fetch: A WHATWG-compatible fetch API.
    • NitroWebSocket: A native WebSocket implementation (using libwebsockets + mbedTLS) that matches the browser WebSocket shape.
    • NitroTextDecoder: A native UTF-8 decoder designed to outperform the Hermes JS polyfill.

    The ecosystem focuses on three performance pillars: Prefetching (running requests/socket opens before JS loads), Native Client Integration (explicit imports or global replacement), and Observability (using NetworkInspector for JS-level debugging or native Perfetto/Instruments for low-level latency analysis).

  2. Overview of react-native-nitro-fetch

    main

    react-native-nitro-fetch is a high-performance, general-purpose network fetching library for React Native. It is designed as a drop-in replacement for the standard fetch() API while providing advanced features like prefetching, workletized data mapping, and support for modern protocols.

    Key features include:

    • High Performance: Uses Cronet on Android and URLSession on iOS.
    • Modern Protocol Support: Supports HTTP/1, HTTP/2, and HTTP/3 (over QUIC), Brotli compression, and disk caching.
    • Prefetching: Ability to prefetch data on app startup to reduce initialization latency.
    • Worklet Support: Enables parallel data mapping on separate threads to avoid blocking the JavaScript thread.
    • Extensibility: Optional WebSocket support via react-native-nitro-websockets and powered by the Nitro Modules architecture.
  3. Avoid monkey-patching globalThis.TextDecoder

    main

    Do not replace globalThis.TextDecoder with the react-native-nitro-text-decoder implementation. Because this package only supports UTF-8, libraries that expect other encodings (like utf-16le) will throw errors if they use the nitro polyfill.

    Instead, use one of these patterns:

    1. Use the library's native output and decode the bytes yourself using an explicit TextDecoder import.
    2. Pass a decoder to the library if it accepts one as an option.
    3. Leave the library on Hermes' built-in TextDecoder (it will be slower, but safer).
  4. How WebSocket pre-warming works

    main

    WebSocket pre-warming is a side-channel optimization designed to eliminate the 500–1500ms delay caused by TCP, TLS, and HTTP upgrade handshakes during a React Native cold start.

    The Mental Model:

    1. Persist Intent: You call prewarmOnAppStart (e.g., after a user logs in) to save a URL and its headers to native storage.
    2. Native Bootstrap: On the next app launch, before the JS engine starts, native code reads the queue and opens the connection on a background C++ thread. Any messages sent by the server during this boot phase are buffered.
    3. JS Adoption: When your JS code eventually calls new NitroWebSocket(url, ...) with the exact same URL, the native layer hands over the already-open connection. The buffered messages are then replayed to your onmessage handler.

    Important: Pre-warming is for the next launch. Calling pre-warm and then immediately constructing a socket in the same session provides no benefit.

    import { NitroWebSocket } from 'react-native-nitro-websockets';
    
    // The URL must match the pre-warmed URL exactly (including trailing slashes)
    const ws = new NitroWebSocket(
      'wss://stream.example.com/feed', 
      ['v1.feed.proto'],
      { Authorization: `Bearer ${token}` }
    );
  5. Handle authentication for native cold starts with Token Refresh

    main

    When using prefetchOnAppStart or WebSocket prewarming (react-native-nitro-websockets), native code executes before your JavaScript bundle. If these requests require authentication, you must register a token refresh configuration.

    On every cold start, the native layer will automatically call your specified refresh URL, map the response into the required headers or bodies, and merge them into your auto-prefetches or WebSocket prewarms. The configuration and resulting tokens are stored in platform-secure storage (Android Keystore/SharedPreferences and iOS Keychain).

    import { registerTokenRefresh } from 'react-native-nitro-fetch';
    
    registerTokenRefresh({
      target: 'fetch', // 'websocket' | 'fetch' | 'all'
      url: 'https://api.example.com/oauth/token',
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ grant_type: 'client_credentials' }),
      responseType: 'json',
      mappings: [
        {
          jsonPath: 'access_token',
          header: 'Authorization',
          valueTemplate: 'Bearer {{value}}',
        },
      ],
      onFailure: 'useStoredHeaders', // 'useStoredHeaders' (default) or 'skip'
    });
  6. Interpret native trace results

    main

    Use these patterns to diagnose performance issues in Perfetto or Instruments:

    ObservationMeaning
    Long NitroFetch GET /x interval + JS thread idleNetwork layer issue (DNS, TLS, server, or transport)
    Long interval ending exactly when JS thread spikesBody parsing is the bottleneck (use profileFetch to confirm)
    Two non-overlapping intervals for the same URLCache miss (prefetching is not being adopted)
    Long NitroWS connect <url> intervalSlow TLS handshake (consider pre-warming the socket)
    NitroWS receive events bursting at launch with no JS handlerConnection opened before JS was ready; events are buffering correctly
  7. Understand iOS networking implementation (URLSession)

    main
    On iOS, react-native-nitro-fetch does not use Chromium Cronet. Instead, it is built entirely on Apple's native URLSession. This provides support for HTTP/1.1, HTTP/2, and HTTP/3 (on recent iOS versions) directly from the operating system. While the shared Nitro spec uses the name NitroCronet, this refers to the streaming-client interface, which is backed by URLSession on iOS.
  8. Use local and non-HTTP URLs with fetch

    main

    In addition to http(s), fetch(...) supports several other URI schemes for reading local resources natively:

    • data: URLs: Decoded in JS. If react-native-nitro-text-decoder is bundled, it uses that; otherwise, it falls back to a global TextDecoder. If neither is available, the body remains accessible via res.arrayBuffer() or res.bytes().
    • file:// URLs and bare absolute paths: Read directly off disk natively.
    • content:// URIs: Read via the ContentResolver on Android.

    Important Limitations:

    • blob: URLs are not supported and will reject with a TypeError because the React Native blob registry is not reachable from native.
    • Local files return a 200 Response with a Content-Type automatically guessed from the file extension or the data: media type.
    // data: URLs are decoded in JS
    await fetch('data:text/plain;base64,SGVsbG8='); // -> "Hello"
    
    // file:// URLs and bare absolute paths are read off disk natively
    await fetch('file:///var/mobile/.../import.csv');
    await fetch('/var/mobile/.../import.csv'); // scheme-less absolute path
    
    // content:// URIs are read via the ContentResolver (Android)
    await fetch('content://com.android.providers.../document/1234');
    
    // Example: reading local files
    import { fetch } from 'react-native-nitro-fetch';
    
    const csv = await (await fetch(`file://${pickedFileUri}`)).text();
    const config = await (await fetch(`${cacheDir}/config.json`)).json();
  9. Performance characteristics of nitro-fetch

    main

    Even without using the prefetch feature, nitro-fetch is faster than React Native's built-in fetch. In testing, it is approximately 15–25% faster for end-to-end requests.

    Performance gains are highly dependent on backend configuration. The performance gap between nitro-fetch and built-in fetch widens significantly when using:

    • Connection reuse
    • HTTP/2
    • HTTP/3 over QUIC
  10. Understand the Cronet Engine configuration on Android

    main

    On Android, react-native-nitro-fetch uses a shared CronetEngine instance that is created lazily and persists for the lifetime of the process. The engine is configured with the following defaults:

    • Protocols: HTTP/2, HTTP/3 (QUIC), and Brotli compression are enabled.
    • Disk Cache: A 50 MB disk cache is located at <cacheDir>/nitrofetch_cronet_cache (using HTTP_CACHE_DISK).
    • User-Agent: NitroFetch/1.0.
    • Threading: Callbacks are executed on a fixed-size NitroCronet-io thread pool.
    • Provider Selection: The engine logs available CronetProviders and prefers providers containing "Native" in their name to avoid Play Services DNS quirks, falling back to the default provider if necessary.

    To clean up resources, you can call NitroFetch.shutdown() to tear down the engine (best-effort).