React Native Image Assets: @2x and @3x Explained

Learn how Metro bundler resolves density-specific images, when to use local vs remote assets, and how to optimize image performance in React Native apps.

7 min read·Updated May 2026·App Image Kit

How React Native Handles Image Assets

React Native renders truly native UI components, but image handling follows a convention borrowed from iOS. When you reference a local image via require(), the Metro bundler scans the same directory for files with @1.5x, @2x, and @3x suffixes. At runtime, the framework picks the variant that best matches the device's pixel density, so the user always sees a sharp image without downloading unnecessary data.

This density-selection mechanism is conceptually identical to the way iOS asset catalogs work with points and pixels. If you are already familiar with how DPI works, the React Native model will feel natural.

The @1x / @2x / @3x Naming Convention

Place your image files in the same directory and name them with scale suffixes. Metro bundler uses the base filename (without the suffix) as the logical asset identifier and resolves the correct variant automatically.

src/assets/
  logo.png            ← 1x baseline (e.g. 100 × 100 px)
  [email protected]         ← 2x variant  (200 × 200 px)
  [email protected]         ← 3x variant  (300 × 300 px)

The @1.5x suffix is also supported and maps to Android's hdpi bucket (~240 dpi), though most projects skip it in favour of the three standard tiers.

Scale suffixPixel ratioAndroid equivalentExample devices
@1x1.0mdpi (~160 dpi)Older low-density Android devices
@1.5x1.5hdpi (~240 dpi)Budget Android phones
@2x2.0xhdpi (~320 dpi)iPhone SE, iPhone 14, most iPads
@3x3.0xxhdpi (~480 dpi)iPhone Pro, iPhone Plus, flagship Android

For a deeper look at Android drawable folders and how they map to physical screens, see the Android drawable sizes guide.

Using require() and the Image Component

Static images are imported at build time with require(). Metro resolves the correct density variant, bundles only the needed file, and provides the width and height metadata so the layout engine can reserve space before the image loads.

import { Image, StyleSheet } from 'react-native';

export default function Logo() {
  return (
    <Image
      source={require('../assets/logo.png')}
      style={styles.logo}
      accessibilityLabel="App logo"
    />
  );
}

const styles = StyleSheet.create({
  logo: { width: 100, height: 100 },
});

You only reference logo.png in the require call. Metro automatically substitutes [email protected] or [email protected] depending on the device's screen density.

Remote Images vs Local Assets

Not every image belongs in the app bundle. Remote images loaded from a CDN or API are appropriate for user-generated content, product catalogues, and anything that changes frequently. Local assets are better for branding, icons, onboarding illustrations, and anything that must render instantly without a network round-trip.

// Remote image — width and height are required
<Image
  source={{ uri: 'https://cdn.example.com/photo.jpg' }}
  style={{ width: 300, height: 200 }}
/>

// Local asset — dimensions inferred from file metadata
<Image source={require('../assets/hero.png')} />

Remote images require explicit width and height style properties because Metro cannot inspect the file at build time. Omitting dimensions causes the image to render at 0x0 pixels.

Platform-Specific Images

React Native supports platform-specific file extensions. If your design calls for different assets on Android and iOS, add .android.png and .ios.png suffixes. Metro selects the correct file at build time.

src/assets/
  header.android.png
  [email protected]
  [email protected]
  header.ios.png
  [email protected]
  [email protected]

This works with the same require('../assets/header.png') call. Metro resolves both the platform extension and the scale suffix automatically. Use this sparingly — maintaining two complete asset sets doubles your workload. It is most useful when platform-specific UI guidelines demand different visual treatments, such as navigation bar icons.

App Icons and Splash Screens

App icons live outside the JavaScript bundle and must be configured natively. Each platform requires a grid of specific sizes. iOS needs up to 20 icon variants for App Store, Spotlight, Settings, and Notifications. Android needs adaptive icons with foreground and background layers in every drawable bucket. See the full lists in the iOS app icon sizes and Android drawable sizes guides.

The community package react-native-make can automate this. Run npx react-native set-icon --path ./icon-1024.png and it generates every required size for both platforms. Alternatively, App Image Kit lets you export all density variants from a single high-resolution source image in the browser, with no installation required.

Performance Optimization

Images are often the largest assets in a mobile app. Poor image handling leads to high memory consumption, janky scrolling, and slow startup times. React Native provides several mechanisms to keep performance under control.

resizeMode

The resizeMode prop controls how an image is scaled to fit its container. The most common values are cover (fills the container, may crop), contain (fits entirely within the container), and stretch (fills exactly, may distort). Choosing the correct mode avoids unnecessary decoding of oversized images.

Caching with react-native-fast-image

The built-in Image component does not cache remote images aggressively, especially on Android. The react-native-fast-image library wraps SDWebImage (iOS) and Glide (Android) to provide reliable disk and memory caching, priority loading, and preloading.

import FastImage from 'react-native-fast-image';

<FastImage
  source={{
    uri: 'https://cdn.example.com/photo.jpg',
    priority: FastImage.priority.high,
    cache: FastImage.cacheControl.immutable,
  }}
  resizeMode={FastImage.resizeMode.cover}
  style={{ width: 300, height: 200 }}
/>

Memory usage

Every decoded image consumes width × height × 4 bytes of RAM (RGBA). A 3000x3000 source image takes roughly 36 MB when fully decoded. On a 3x device that only renders at 300x300 logical points (900x900 physical pixels), most of that memory is wasted. Always resize images to the maximum display size before bundling. Supplying correct @2x and @3x variants instead of a single oversized file is the single most impactful optimisation you can make.

Organising Image Assets

A clean folder structure prevents asset sprawl as your project grows. A widely adopted convention is to group images by feature or screen, with a shared directory for global assets like logos and illustrations.

src/
  assets/
    common/
      logo.png
      [email protected]
      [email protected]
    onboarding/
      step1.png
      [email protected]
      [email protected]
    profile/
      avatar-placeholder.png
      [email protected]
      [email protected]

Keep all three density variants side by side. If a file is missing a @3x variant, Metro falls back to the closest available scale, which may produce a noticeably blurry result on high-density devices. Use a tool like App Image Kit to generate every density from a single source and avoid accidentally skipping a variant.

Best Practices Checklist

Always start from the highest resolution source

Design at @3x (or higher) and scale down. Upscaling a low-resolution image produces blurry results.

Supply all three density variants

Missing variants force Metro to scale at runtime, wasting CPU cycles and producing softer images.

Avoid bundling oversized images

A 4000x4000 image rendered at 100x100 points wastes tens of megabytes of RAM after decoding.

Use FastImage for remote image lists

The built-in Image component lacks aggressive caching. FastImage prevents redundant network requests in scrollable lists.

Do not hard-code pixel dimensions for all screens

Use flexbox, percentage widths, or Dimensions API to adapt layouts to different screen sizes.

Compress PNGs and convert to WebP where supported

Smaller files reduce bundle size and decode faster. React Native supports WebP on both platforms since version 0.61.

Summary

React Native's image system is straightforward once you understand the naming convention. Provide @1x, @2x, and @3x variants for every local asset, use require() for static imports, and lean on react-native-fast-image for remote URLs. Keep images sized to their actual display dimensions to control memory usage, and automate density generation with App Image Kit so you never ship a blurry asset again.

For related topics, explore the DPI fundamentals guide, the breakdown of dp vs px vs pt, and the platform-specific references for iOS app icon sizes and Android drawable sizes.

Ready to export your assets?

App Image Kit generates all density variants from a single source image — free, private, and instant.