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 Content | Approximate Size |
|---|---|
| 10 icons | ~5 KB |
| 100 icons | ~50 KB |
| 500 icons | ~250 KB |
| Full MDI (7,447) | ~3.5 MB |
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โ
| Option | Type | Default | Description |
|---|---|---|---|
skipExisting | boolean | true | Skip icons already in cache |
verbose | boolean | false | Log loading progress |
batchSize | number | 50 | Icons to load per batch |
onProgress | (loaded, total) => void | - | Progress callback |
Next Stepsโ
- Babel Plugin - Build-time bundling alternative
- Custom Server - Self-hosted icon server
- Architecture - How bundling works