Fast, native gzip compression and decompression for React Native, built on top of Nitro Modules and zlib.
This library is a drop-in replacement for react-native-gzip that:
- Uses Nitro Modules instead of the legacy bridge — direct C++/JSI calls, no JSON serialization, no async queue overhead.
- Runs the actual compression on a background thread so the JS thread stays free.
- Ships a small, dependency-free native core (
zlib+ a tiny inline base64 codec — no third-party native deps). - Works on both iOS and Android, with the New Architecture (Fabric/TurboModules) enabled.
- Includes batch APIs (
inflateBatch/deflateBatch) that fan a single JS call out across multiple native worker threads — ideal for hot paths that (de)compress many payloads back-to-back.
react-native-gzip works, but it goes through the old bridge: every call serializes the input/output as a string across the bridge and queues the work on the module method queue. For large payloads (think: API responses, sync uploads, log batches) that's measurable.
react-native-gzip-nitro exposes the same two functions (inflate / deflate) over JSI via Nitro, so:
- The string crosses the JS/Native boundary as a
std::stringdirectly — no JSON. - The native work runs on a Nitro background thread pool; you get a real
Promiseback. - The C++ implementation is shared verbatim between iOS and Android (same
cpp/HybridGzip.cppfile).
- React Native 0.74+ (New Architecture enabled — required by Nitro).
- iOS 13.4+.
- Android minSdk 26+, NDK 27+.
react-native-nitro-modulesinstalled in the host app.
yarn add react-native-gzip-nitro react-native-nitro-modules
# or
npm install react-native-gzip-nitro react-native-nitro-modulesThen install pods:
cd ios && pod installThat's it — autolinking handles the rest on both platforms.
⚠️ New Architecture is required. Make surenewArchEnabled=trueinandroid/gradle.propertiesandRCT_NEW_ARCH_ENABLED=1in your iOS environment.
import { inflate, deflate, inflateBatch, deflateBatch } from "react-native-gzip-nitro";
// Compress a string -> base64 of gzip bytes
const compressed = await deflate("Hello, world!");
console.log(compressed); // e.g. "H4sIAAAAAAAAA/NIzcnJ1w..."
// Decompress base64-encoded gzip back to the original UTF-8 string
const original = await inflate(compressed);
console.log(original); // "Hello, world!"
// Process many payloads in a single native call, in parallel
const payloads = ["a", "b", "c"];
const compressedBatch = await deflateBatch(payloads);
const originals = await inflateBatch(compressedBatch);
console.log(originals); // ["a", "b", "c"]Gzip-compresses a UTF-8 string and returns a base64-encoded string of the gzip bytes.
- The output uses standard gzip framing (RFC 1952), with default compression level (
Z_DEFAULT_COMPRESSION) and the standard 15+16 window-bit configuration. - Runs on a background thread.
Decompresses a base64-encoded gzip (or zlib) payload and returns the original UTF-8 string.
- Internally uses
inflateInit2(..., 15 + 32), so it auto-detects both gzip (RFC 1952) and zlib (RFC 1950) framing — you can decode either. - Whitespace inside the base64 input (newlines, CR, spaces, tabs) is tolerated.
- Runs on a background thread.
Gzip-compresses an array of UTF-8 strings in a single native call and returns the base64-encoded results in the same order as the input.
- Items are dispatched across native worker threads (capped at
std::thread::hardware_concurrency()), so a batch of N items completes roughly inceil(N / cores)× per-item time instead ofN ×per-item time. - An empty array resolves to an empty array.
- Fail-fast: if any item throws, the whole batch rejects with the first error encountered.
Decompresses an array of base64-encoded gzip (or zlib) payloads in a single native call and returns the UTF-8 strings in the same order as the input.
- Same parallel-execution and fail-fast semantics as
deflateBatch. - Each item independently auto-detects gzip vs. zlib framing, so a mixed batch is fine.
The batch APIs are best when you have ≥2 payloads to (de)compress at once: you amortize the JSI hop across all of them and exploit multiple cores. For a single payload, prefer inflate / deflate — there's no parallelism win, and you skip allocating a 1-element array.
inflate / deflate (and each item inside inflateBatch / deflateBatch) reject the returned promise with a JS Error if:
- the base64 input contains an invalid character (
inflate), - the gzip stream is truncated or corrupted (
inflate), - zlib fails to initialize (
Z_MEM_ERROR, etc.).
For the batch variants, the first error wins and the whole promise rejects — successfully processed items are discarded.
try {
await inflate("!!!not base64!!!");
} catch (err) {
// err.message will include the underlying zlib message when available.
} ┌─────────────────────────────┐
deflate("...") ──► │ JS (your app) │
└────────────┬────────────────┘
│ JSI (Nitro hybrid object)
┌────────────▼────────────────┐
│ C++ HybridGzip │
│ ├─ base64Decode/Encode │
│ └─ zlib (deflate/inflate) │
└────────────┬────────────────┘
│ background thread
┌────────────▼────────────────┐
│ Promise<std::string> │
└─────────────────────────────┘
The TypeScript spec is in src/Gzip.nitro.ts; the C++ implementation lives in cpp/HybridGzip.cpp. Nitrogen-generated bindings live under nitrogen/generated/ and are committed to the repo so consumers don't need to run codegen themselves.
git clone https://github.com/gustavomts/react-native-gzip-nitro.git
cd react-native-gzip-nitro
yarn install
yarn test # runs the JS-level inflate/deflate tests
yarn specs # regenerates the Nitrogen bindings if you edit Gzip.nitro.tsTo test inside an app, point your package.json at a local checkout:
{
"dependencies": {
"react-native-gzip-nitro": "file:../react-native-gzip-nitro"
}
}| Path | What's in it |
|---|---|
src/ |
TypeScript spec (Gzip.nitro.ts) and JS entry point (index.ts) |
cpp/ |
Cross-platform C++ implementation (HybridGzip.{hpp,cpp}) |
android/ |
Gradle module + CMake setup, links against libz |
ios/ |
iOS bits (most logic is in cpp/) |
nitrogen/generated/ |
Nitrogen output — regenerate via yarn specs |
__tests__/ |
Jest tests for the gzip round-trip on Node's zlib |
The exported function names and signatures match react-native-gzip (deflate/inflate, base64 in/out, Promise<string>), so most apps can swap the import:
- import { deflate, inflate } from "react-native-gzip";
+ import { deflate, inflate } from "react-native-gzip-nitro";The output bytes are byte-compatible: gzip with a standard 32 KB window, default compression level. Anything that decompresses react-native-gzip's output will decompress this library's output, and vice versa.
inflateBatch / deflateBatch are additive — they have no equivalent in react-native-gzip and don't change the behavior of the single-payload functions.
MIT © Gustavo Aires
- Margelo — for the excellent Nitro Modules framework.
- zlib — the reference compression library this is built on.
react-native-gzip— the API this library mirrors.