Open-source project
mrousavy/react-native-nitro-image avatar
mrousavy/react-native-nitro-image

react-native-nitro-image: an Image type that lives in memory

🖼️ A superfast in-memory Image type and view component for React Native, built with Nitro!

654 stars41 forksTypeScriptMIT

At a glance

What is it?
react-native-nitro-image is a Nitro Modules backed image core type and view component for React Native that keeps pixel data in memory so images can be resized, cropped, rotated and re-encoded without touching the filesystem, at the cost of requiring the new architecture and a separate package for web images.
Who is it for?
react-native-nitro-image fits React Native teams that need image work beyond what a source URI allows, in particular transform-then-display flows, large lists backed by deferred loaders, and placeholders generated from ThumbHash. It does not fit a project still on the old architecture, a project that only needs to display remote images, or a team unwilling to own two npm packages and an iOS Podfile tweak.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 39 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

An object that owns pixels instead of a view that owns a URI

The difference between this package and the image handling you already have in a React Native app is one of ownership. The project describes its Image as an instance-based type with byte-buffer pixel data access, and the view component accepts that instance. Once you hold an Image, you can work on it: resize, crop, rotate and mirror happen entirely in memory, without ever writing to or reading from a file. You can also move the pixels between representations, since the type converts to and from raw pixel data in RGB format, to and from encoded data in jpg, png or heic, and to a Base64 string. The view is one consumer of that object rather than the only way to see an image. The repository is MIT licensed, the last push was on 2026-09-14, and the current release line is v0.15.2 from 2026-08-20.

Nitro bindings and the hard new architecture requirement

The native side comes from Nitro Modules, which the project credits for efficient native bindings, and the view itself is built with Nitro Views. That has one consequence you cannot work around: the new architecture has to be enabled in your React Native app. The documentation states this as a requirement rather than a recommendation, so on a codebase that has not migrated, this package is not an option at all. The other structural choice worth knowing is that the web side is deliberately not included. To keep the core lightweight, the package ships no web image loader or caching system, and SDWebImage on iOS and Coil on Android come from the separate web image package. The core package is therefore small on purpose, and the web capability is something you opt into.

Installing: two npm packages and a pod install

The installation is short. You install the image package and Nitro Modules itself, then install the CocoaPods dependencies for iOS:

sh
npm i react-native-nitro-image
npm i react-native-nitro-modules
cd ios && pod install

The web loader is a second installation with its own pod step:

sh
npm i react-native-nitro-web-image
cd ios && pod install

A minimal component looks like this, where the image source is a file path and the size is expressed as a plain style object:

tsx
function App() {
  return (
    <NitroImage
      image={{ filePath: '/tmp/image.jpg' }}
      style={{ width: 400, height: 400 }}
    />
  )
}

The path conventions are worth reading carefully. Local file APIs take filesystem paths rather than file:// URLs, and the documentation points out that loading a local file with the web loader instead means passing it as a url with a file:// prefix. The repository behind these two packages is a monorepo: a private root package named react-native-nitro-image-monorepo whose workspaces list packages/react-native-nitro-image, packages/react-native-nitro-web-image and an example application holding the ios and android projects. The toolchain is bun, linting is Biome, and releases go through release-it with conventional changelog plugins, so a version bump in one package lands in the other through the monorepo rather than through separate repositories.

The Podfile needs modular headers for SDWebImage

There is one platform-specific step that is easy to miss. SDWebImage does not enable modular headers for static linkage, so if you install the web image package on iOS you have to enable them yourself in your app's Podfile:

rb
target '…' do
  config = use_native_modules!

  # Add this line:
  pod 'SDWebImage', :modular_headers => true
end

The comment in that snippet is the author's own, and the instruction is unconditional. The reason to know this in advance is that the failure appears at build time on iOS rather than at import time in JavaScript, so it is the kind of problem that costs an afternoon if you did not expect it. On Android nothing analogous is required, because the web loader there is built on Coil.

Resizing, cropping and rotating without a file in sight

This is where the in-memory design pays off. Every transformation is an awaitable method on the image object, and none of them touches the filesystem:

ts
const webImage = await WebImages.loadFromURLAsync('https://picsum.photos/seed/123/400')
const smaller  = await webImage.resizeAsync(200, 200)

The same pattern covers cropping with cropAsync and its offset and size arguments, rotating with rotateAsync and an angle, and mirroring horizontally. The important property is not the individual calls but the composition. You can decode once, transform several times, and hand the result to the view, instead of paying for a temporary file and a re-decode per step. The same idea shows up in the file APIs, where saveToTemporaryFileAsync writes an encoded result when you actually do need a file, with a format and a quality argument.

Raw pixel data, encoded data and the Base64 trap

The Image type speaks three interchange formats and the differences matter. Raw pixel data is an ArrayBuffer with the pixels in RGB format, produced by toRawPixelData and read back with Images.loadFromRawPixelData. Encoded data is an ArrayBuffer in a container such as jpg, png or heic, produced by toEncodedImageData and read back with Images.loadFromEncodedImageData. Base64 is a string produced by toBase64Async, and the documentation is precise about its shape: it returns a padded payload without line breaks and without a data URL prefix, so a data URL has to be assembled by hand if you need one. The note attached to it is the warning worth keeping. Base64 is larger than the binary image data, and toEncodedImageDataAsync should be preferred unless the destination specifically requires a string.

Deferred loaders for long lists and preload for what is coming

Rendering a thousand images is where an image library earns its place, and this one has a specific answer for it: deferred ImageLoader types, which the project lists as a way to optimise for displaying large lists of images. The API surface is small. loadImage returns a loaded image from a url, a filePath, a resource, a symbol name, or the result of a require call. There are also loader options, with a priority setting that takes low or high when loading from a remote URL, and a separate preload call for images you know will be rendered soon, for example a large profile picture that appears when a row is opened. There is also a blank image constructor, Images.createBlankImage with width, height, an enableAlpha flag and an optional RGB colour, which is useful as a placeholder while the real bytes are still arriving.

Where Nitro Image is the wrong choice

Two situations point away from this package. The first is a codebase that has not moved to the new architecture, where the requirement is absolute and the alternative is to keep using whatever image component the project already has. The second is an app that only displays remote images and never transforms them. In that case the cost is real: a dependency on Nitro Modules, a second package for web loading, a Podfile change on iOS, and a view component that has to learn the new object model, in exchange for transformation features you are not using. The alternative approach there is the plain URI-based image view that ships with React Native, where the source is a URL or a bundled resource and the platform handles decoding. Nitro Image becomes the better choice when the pipeline needs the pixels themselves: a thumbnail derived from a downloaded original, a re-encode before upload, a placeholder generated from a ThumbHash, or a list that must not fetch full-size images up front.

Editorial conclusion

react-native-nitro-image fits React Native teams that need image work beyond what a source URI allows, in particular transform-then-display flows, large lists backed by deferred loaders, and placeholders generated from ThumbHash. It does not fit a project still on the old architecture, a project that only needs to display remote images, or a team unwilling to own two npm packages and an iOS Podfile tweak. Before adopting it, confirm the new architecture is enabled, install react-native-nitro-web-image separately if you load from the network, and decide whether you actually need Base64 output, since toEncodedImageDataAsync is the smaller path when the destination accepts bytes.

Frequently asked questions

Does react-native-nitro-image require the new architecture?

Yes. Since NitroImage is built with Nitro Views, the new architecture has to be enabled in your React Native app, so the package is not an option on a project that has not migrated.

How do I load an image from the network with Nitro Image?

Use loadImage with a url, or WebImages.loadFromURLAsync with options such as a priority of low or high. The web loader and caching live in the separate react-native-nitro-web-image package, which the core package does not ship.

Can I resize or crop an image without writing a file?

Yes. resizeAsync, cropAsync, rotateAsync and the mirroring helper all run entirely in memory, and you can chain them on one decoded image before handing the result to the view.

When should I use toBase64Async instead of toEncodedImageDataAsync?

Only when the destination specifically requires a string. toBase64Async returns a padded payload without line breaks or a data URL prefix, and Base64 is larger than the binary image data, so the encoded data path is the smaller one.

What extra setup does the web image package need on iOS?

SDWebImage does not enable modular headers for static linkage, so you have to add pod 'SDWebImage', :modular_headers => true to your app's Podfile yourself, then run pod install again.

Official sources

  1. License: MIT
  2. mrousavy/react-native-nitro-image on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mrousavy-react-native-nitro-image.svg)](https://hysenlabs.com/projects/mrousavy-react-native-nitro-image)

Was this useful?