Skip to main content

Offline Bundles

Create and load icon bundles for complete offline support without any network requests.

When to Use Offline Bundlesโ€‹

  • Air-gapped environments: No network access
  • Guaranteed availability: Critical icons always available
  • Reduced latency: Zero network delay
  • Compliance: Strict network policies

Creating Bundlesโ€‹

CLI Methodโ€‹

Generate a bundle using the CLI:

npx rn-iconify bundle \
--icons "mdi:home,mdi:settings,heroicons:user" \
--output ./src/icons/bundle.json

Loading Bundlesโ€‹

Load the bundle at app startup:

import { loadOfflineBundle } from 'rn-iconify';
import iconBundle from './icons/bundle.json';

// Load before rendering any icons
loadOfflineBundle(iconBundle);

function App() {
return (
<>
<Mdi name="home" /> {/* Loads instantly from bundle */}
<Mdi name="settings" />
</>
);
}

Bundle Formatโ€‹

The bundle JSON structure:

{
"version": "2.0.0",
"generatedAt": "2025-12-03T10:30:00Z",
"count": 2,
"icons": {
"mdi:home": {
"svg": "<path d=\"M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z\"/>",
"width": 24,
"height": 24
},
"mdi:settings": {
"svg": "<path d=\"...\"/>",
"width": 24,
"height": 24
}
}
}

Bundle by Icon Setโ€‹

Create separate bundles per icon set:

# Material Design Icons
npx rn-iconify bundle --icons "mdi:*" --output ./bundles/mdi.bundle.json

# Heroicons
npx rn-iconify bundle --icons "heroicons:*" --output ./bundles/heroicons.bundle.json

Load specific bundles:

import mdiBundle from './bundles/mdi.bundle.json';
import heroiconsBundle from './bundles/heroicons.bundle.json';

// Load only what you need
loadOfflineBundle(mdiBundle);
loadOfflineBundle(heroiconsBundle);

Lazy Loading Bundlesโ€‹

Load bundles on demand:

import { loadOfflineBundle } from 'rn-iconify';

async function loadIconSet(setName: string) {
let bundle;

switch (setName) {
case 'mdi':
bundle = await import('./bundles/mdi.bundle.json');
break;
case 'heroicons':
bundle = await import('./bundles/heroicons.bundle.json');
break;
}

if (bundle) {
loadOfflineBundle(bundle);
}
}

// Load when entering a screen that uses specific icons
useEffect(() => {
loadIconSet('heroicons');
}, []);

Bundle Optimizationโ€‹

Analyze Usage Firstโ€‹

# See which icons you actually use
npx rn-iconify analyze --src ./src

# Generate bundle with only used icons
npx rn-iconify bundle \
--icons "mdi:home,mdi:settings,mdi:account" \
--output ./bundle.json

Size Comparisonโ€‹

Bundle ContentApproximate Size
10 icons~5 KB
100 icons~50 KB
500 icons~250 KB
Full MDI (7,447)~3.5 MB
warning

Only bundle icons you actually use. Analyze your codebase first with npx rn-iconify analyze.

Hybrid Modeโ€‹

Combine offline bundles with runtime fetching:

import { loadOfflineBundle, configure } from 'rn-iconify';
import criticalIcons from './bundles/critical.json';

// Load critical icons from bundle
loadOfflineBundle(criticalIcons);

// Configure API settings for non-bundled icons
configure({
api: {
timeout: 5000,
retries: 2,
},
});

OTA Bundle Updatesโ€‹

Update bundles without app store releases:

import { loadOfflineBundle } from 'rn-iconify';
import AsyncStorage from '@react-native-async-storage/async-storage';

async function updateIconBundle() {
try {
// Fetch latest bundle from your server
const response = await fetch('https://your-server.com/icons/bundle.json');
const bundle = await response.json();

// Save for offline use
await AsyncStorage.setItem('iconBundle', JSON.stringify(bundle));

// Load immediately
loadOfflineBundle(bundle);
} catch (error) {
// Fall back to stored bundle
const stored = await AsyncStorage.getItem('iconBundle');
if (stored) {
loadOfflineBundle(JSON.parse(stored));
}
}
}

Build Integrationโ€‹

For build-time icon bundling, use the CLI command in your build scripts:

// package.json
{
"scripts": {
"prebuild": "npx rn-iconify bundle --auto --output ./assets/icons.bundle.json"
}
}

Or use the Babel plugin for automatic bundling during compilation. See Babel Plugin for details.

TypeScript Supportโ€‹

Create type-safe bundles by importing the JSON with TypeScript:

// icons.ts
import iconBundle from './assets/icons.bundle.json';
import { loadOfflineBundle, IconBundle } from 'rn-iconify';

// Type-safe bundle loading
loadOfflineBundle(iconBundle as IconBundle);

// Extract bundled icon names for type safety
export type BundledIconName = keyof typeof iconBundle.icons;

Best Practicesโ€‹

1. Bundle Critical Iconsโ€‹

// Critical icons - always bundled
const criticalIcons = [
'mdi:home',
'mdi:menu',
'mdi:close',
'mdi:arrow-left',
];

2. Keep Bundles Smallโ€‹

# Bad - bundles everything
npx rn-iconify bundle --icons "mdi:*"

# Good - bundles only what's used
npx rn-iconify bundle --icons "mdi:home,mdi:settings,mdi:user"

3. Update Bundles Regularlyโ€‹

// package.json
{
"scripts": {
"icons:bundle": "rn-iconify bundle --icons \"mdi:home,mdi:settings\" --output ./src/icons/bundle.json",
"prebuild": "npm run icons:bundle"
}
}

Troubleshootingโ€‹

Check Bundled Iconsโ€‹

import { CacheManager } from 'rn-iconify';

// Check bundled count
const stats = CacheManager.getStats();
console.log('Bundled icons count:', stats.bundledCount);

Icon Not in Bundleโ€‹

import { CacheManager } from 'rn-iconify';

if (!CacheManager.hasBundled('mdi:home')) {
console.warn('mdi:home not in bundle, will fetch from network');
}

Bundle Utilitiesโ€‹

isBundleCompatibleโ€‹

Check if a bundle is compatible with the current version:

import { isBundleCompatible } from 'rn-iconify';
import iconBundle from './bundle.json';

if (isBundleCompatible(iconBundle)) {
loadOfflineBundle(iconBundle);
} else {
console.warn('Bundle version mismatch, regenerate bundle');
}

getBundleStatsโ€‹

Get detailed statistics about a bundle:

import { getBundleStats } from 'rn-iconify';
import iconBundle from './bundle.json';

const stats = getBundleStats(iconBundle);

console.log({
iconCount: stats.iconCount,
prefixes: stats.prefixes, // ['mdi', 'heroicons']
sizeBytes: stats.estimatedSizeBytes,
generatedAt: stats.generatedAt,
});

Async Loading with Progressโ€‹

loadOfflineBundleAsyncโ€‹

Load large bundles asynchronously with progress tracking:

import { loadOfflineBundleAsync } from 'rn-iconify';
import { useState } from 'react';

function App() {
const [progress, setProgress] = useState(0);
const [loaded, setLoaded] = useState(false);

useEffect(() => {
loadOfflineBundleAsync(
require('./large-bundle.json'),
{
batchSize: 100, // Load 100 icons per batch
onProgress: (loaded, total) => {
setProgress((loaded / total) * 100);
},
}
).then(() => {
setLoaded(true);
});
}, []);

if (!loaded) {
return (
<View>
<Text>Loading icons: {progress.toFixed(0)}%</Text>
<ProgressBar progress={progress} />
</View>
);
}

return <MainApp />;
}

Optionsโ€‹

OptionTypeDefaultDescription
skipExistingbooleantrueSkip icons already in cache
verbosebooleanfalseLog loading progress
batchSizenumber50Icons to load per batch
onProgress(loaded, total) => void-Progress callback

Next Stepsโ€‹