Skip to documentation
Browse documentation
SimpcnDocsReact Native with Expo

The Simpcn handbook

React Native with Expo

Experimental CDN delivery for static PNG/JPEG images, iOS and Android variants, and screen densities.

Keep your image references

React Native normally packages local image files with the app. Simpcn can replace supported image sources with verified CDN sources during an iOS or Android production build, while leaving your application source unchanged. Development keeps Metro’s normal local image handling.

This integration uses Expo’s Metro configuration. It is not support for bare React Native or Expo web. The tested fixture uses Expo 58.0.14, expo-image 58.0.15, React Native 0.88.0, and React 19.3.0.

Static images: imports and require()

A static image is a local file whose path is known at build time. Both forms below are supported for PNG and JPEG images inside the managed directory:

Your component · stays unchanged
import { Image } from 'react-native';
import logo from './assets/cdn/logo.png';

<Image source={logo} />;
<Image source={require('./assets/cdn/logo.png')} />;

Supported consumers are named imports of React Native Image and ImageBackground, and Image from expo-image. Import aliases are recognized. The build verifies each selected file and emits its CDN URL and image dimensions.

An image import is removed only when all its uses have been rewritten. If the same import is used by a fallback, another consumer, or other application logic, its local asset module remains bundled. Arbitrary strings and variables containing image paths are not automatically rewritten.

Separate iOS and Android images

Use different artwork on each platform without changing the component reference. Metro-style suffixes identify the platform:

One reference, platform-specific files
assets/cdn/
  payment.png          # Generic fallback
  payment.ios.png      # iOS artwork
  payment.android.png  # Android artwork

// Inside a component beside assets/:
<Image source={require('./assets/cdn/payment.png')} />

An iOS production build uses the iOS family’s CDN URLs; Android uses the Android family. A platform without its own family uses the generic family. Once a platform-specific family exists, its densities are selected as a group: missing densities are not filled from generic artwork.

.native families currently remain local. Keep filenames in Metro’s order when combining platform and density: payment@2x.ios.png.

Sharp images with @2x and @3x

Phones have different numbers of physical pixels in the same screen space. Density variants provide sharper copies of an image while keeping its displayed size consistent.

FilePhysical pixelsLogical display size
logo.png24 × 2424 × 24
logo@2x.png48 × 4824 × 24
logo@3x.png72 × 7224 × 24

Keep using require('./logo.png') or the equivalent static import. A small generated helper reads React Native’s device pixel ratio and chooses the first available density at least as large as the device needs. If none is large enough, it uses the largest available. A density of 2.625 selects @3x when available.

You supply the variant files; Simpcn does not resize images or generate @2x/@3x copies. The adapter calculates logical dimensions from pixels divided by density. Inconsistent dimensions, duplicate densities, or missing verification for a selectable sibling fail the build. Families containing only @2x/@3x files can still use the base reference.

Platform and density together
assets/cdn/
  logo.ios.png          # 24 × 24 pixels
  logo@2x.ios.png       # 48 × 48 pixels
  logo.android.png      # 24 × 24 pixels
  logo@3x.android.png   # 72 × 72 pixels

Connect the experimental adapter

Install as a dev dependency
npm i -D simpcn
Commands use npm.

Run the CLI in your Expo application directory. Init can install @simpcn/expo using your project’s package manager:

npm · initialize
npx simpcn init --framework expo

Init detects Expo before Next.js or Vite, defaults the managed root to assets/cdn, and adds native release guidance. It preserves your Metro configuration and prints the wrapper instructions:

metro.config.js · preserve existing settings and wrappers
const { getDefaultConfig } = require('expo/metro-config');
const { withSimpcn } = require('@simpcn/expo/metro');

const config = getDefaultConfig(__dirname);
// Keep your existing Metro configuration here.
module.exports = withSimpcn(config);

Choose a managed directory and release

simpcn.config.ts · keep at the project root
import type { UserConfig } from 'simpcn';

export default {
  root: 'assets/cdn',
  exclude: ['essential/**', 'fallback.png'],
  stateFile: 'simpcn/deployment-state.json',
  manifestFile: 'simpcn/.build/manifest.json',
  storage: {
    provider: 'r2',
    endpoint: 'https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com',
    bucket: 'YOUR_EXISTING_BUCKET',
    region: 'auto',
    prefix: 'releases/mobile-v1',
  },
  publicBaseUrl: 'https://YOUR_PUBLIC_HOST/releases/mobile-v1/',
} satisfies UserConfig;

Use a unique, immutable release prefix and matching public URL. Replacing bytes at a URL used by an older installed app can change that app’s images too. Keep older release namespaces available for installed builds and rollback, and use the same fixed release identifier for migration and bundling.

For R2, the API endpoint is the account origin without the bucket name. The public URL includes the storage prefix once. Credentials stay in local environment files or a secret store. Storage and R2 setup →

Keep essential images local

Keep app icons, splash screens, offline images, and essential fallbacks outside the managed root or add exclusions relative to it. Excluded images retain their local sources. If any selectable density sibling is excluded, that selected family stays local.

CDN images may be unavailable on first launch without a network connection. This adapter does not add automatic fallback or guaranteed offline caching. Fonts, audio/video, SVG components, placeholders, and asset-preloading calls are outside the automatic image transform.

Migrate, then export

npm · preview, migrate, export
npx simpcn plan
npx simpcn migrate
npx expo export --platform ios
npx expo export --platform android

Commit the deployment state with matching assets, config, source, and lockfile. Production compilation checks local verification records and does not upload files or require storage credentials. Generated workers and the mobile density helper live under ignored simpcn/.build/expo/.

Changed images must be migrated again. Restart Metro after changing assets, state, or configuration; fresh export processes invalidate existing Simpcn caches without requiring a cache clear. Live dev-server invalidation has not been established.

What has been verified

Tests cover binding-aware transforms, import retention, logical dimensions, platform precedence, density selection, exclusions, stale siblings, and packed package installation. Real Android/iOS exports contain the expected CDN sources and omit transformed images from the asset graph; mixed-use and excluded assets remain bundled.

These export checks do not establish actual device rendering, downloads, caching, native/EAS release behavior, or OTA update/rollback safety. Namespace component imports, custom wrappers, dynamic requires, duplicate source props, and ambiguous spread props remain outside automatic rewriting. Expo web and bare React Native need separate integrations and validation.

Native reference support → · Release limitations →

Something unclear? Open an issue.

Simpcn v0.1.3 · Experimental integrations are labeled.