Animations are everywhere in today’s consumer apps. Companies use them to grab attention, guide users, and create that memorable, branded feel whether it’s a delightful splash screen, a smooth loading indicator, or festive banners during special seasons. As a React Native developer, we know how much impact good animation can have on the flow / product. Over the last several years, Lottie animations (exported as .json files) have become hugely popular. They make it easy to add rich, scalable animations without impacting performance. But there’s one thing most of us have run into is lottie JSON files can get quite large. Even simple-looking animations can bloat to hundreds of kilobytes or more, especially if they have many animated layers. For apps showcasing several animations like onboarding steps, dynamic banners, achievement badges, and so on, this leads to real challenges. .json The .lottie Format: Smaller and Lighter Lottie supports an optimized format called .lottie. Instead of a raw JSON file, a .lottie animation is a zipped archive that shrinks file size by 70-80% compared to plain JSON - so our 1 MB animation might become 200-300 KB. It also packages all assets (including images) together, reducing missing asset issues. It is ideal for dynamic apps where animation content can change via configs which is great for marketing teams, seasonal banners, or user personalization. optimized .lottie .lottie We typically have two options to include lottie animations in our app Bundling Animations in the AppThis method bundles all your Lottie JSONs within the app bundle-ensuring fast, offline playback. But as your add more animations, your app size increases, and every small animation tweak means a new app release.Loading Animations Over the NetworkAlternatively, you can host your animations on a remote server or CDN (like S3). This keeps your app download lightweight and allows for more flexible updates. The downside? Each time an animation is shown, your app fetches it again. Users with slow or unreliable connections may get stuck waiting-or see failed/missing animations. Plus, it wastes mobile data. Bundling Animations in the App Bundling Animations in the App This method bundles all your Lottie JSONs within the app bundle-ensuring fast, offline playback. But as your add more animations, your app size increases, and every small animation tweak means a new app release. Loading Animations Over the Network Loading Animations Over the Network Alternatively, you can host your animations on a remote server or CDN (like S3). This keeps your app download lightweight and allows for more flexible updates. The downside? Each time an animation is shown, your app fetches it again. Users with slow or unreliable connections may get stuck waiting-or see failed/missing animations. Plus, it wastes mobile data. So whether you bundle or load over the network, switching to .lottie already gives you size benefits. But there is scope for better experience, with just a few small tweaks you can make the whole experience feel a lot smoother and more reliable. .lottie How can we make the .lottie experience even better? .lottie Since .lottie files are just zipped JSON with (optional) images, we can treat them exactly like any standard zip archive: extract them, process the animation JSON however we like, and store a ready-to-use, all-in-one JSON and cache them locally for our app’s next use. This gives us smooth, flexible animation playback-on any device, in any network conditions, every time. Here is a simple workflow we can follow .lottie zipped JSON After these steps, you can load and play the JSON just like any regular bundled Lottie. So whenever you get a .lottie file, first check if a processed json version already exists - if it does, use it. If not, download it, process as json, save it, and reuse it next time. .lottie 1. ⬇️ Download the Animation File 1. Download the Animation File First, download the .lottie and store it locally using a unique name. .lottie const name = extractNameFromUrl(url) const extension = url.match(/\.(json|lottie)$/)?.[1] ?? 'json'; const lottiePath = RNFB.fs.dirs.DocumentDir + `/lottie/${name}.${extension}`; await RNFB.config({ path: lottiePath, }).fetch('GET', url, headers); const name = extractNameFromUrl(url) const extension = url.match(/\.(json|lottie)$/)?.[1] ?? 'json'; const lottiePath = RNFB.fs.dirs.DocumentDir + `/lottie/${name}.${extension}`; await RNFB.config({ path: lottiePath, }).fetch('GET', url, headers); 2. 📦 Unzip the .lottie File 2. Unzip the .lottie File If the animation is in .lottie format, we have to unzip it. This will extract the JSON animation data and any images it references. .lottie if (extension === 'lottie') { const extractPath = `${RNFB.fs.dirs.DocumentDir}/${name}_unzipped`; await unzip(lottiePath, extractPath); } if (extension === 'lottie') { const extractPath = `${RNFB.fs.dirs.DocumentDir}/${name}_unzipped`; await unzip(lottiePath, extractPath); } 3. 🧩 Extract and Process the Animation JSON Inside the unzipped folder, look for an animations folder. Find the animation JSON file: animations const animationsPath = `${extractPath}/animations`; const files = await RNFB.fs.ls(animationsPath); const animFile = files.find(file => file.endsWith('.json')); if (!animFile) throw new Error('No animation JSON found after unzip'); const animPath = `${animationsPath}/${animFile}`; const animationsPath = `${extractPath}/animations`; const files = await RNFB.fs.ls(animationsPath); const animFile = files.find(file => file.endsWith('.json')); if (!animFile) throw new Error('No animation JSON found after unzip'); const animPath = `${animationsPath}/${animFile}`; 4. 🖼️ Embed Images Inside the Animation JSON Sometimes, the lottie uses separate images (say, backgrounds, icons, main assets). To make rendering fast, we can embedd these images right into the JSON file. This means converting image files to base64, then updating the JSON so all images are packed inline. Basically if you inspect the json before embedding it looks like something on the left, now we are finding the actual image with the u and p keys in the json and then converting them to base64 and updating the keys with these values to get our final json. Here u specifies the image folder or directory path and p specifies image filename. base64 u p base64 u p Below is the pseudo code for this process. function embedImagesInLottieJSON(animationJsonPath, imagesFolderPath): // Read the animation JSON as text animationJsonText = readFile(animationJsonPath, asText) animationJson = parse(animationJsonText) // If there are no assets, just return original JSON if animationJson.assets is not an array: return animationJsonText for each asset in animationJson.assets: if asset.u exists AND asset.p exists AND "images" in asset.u AND asset.e == 0: // Get the image file path imagePath = imagesFolderPath + "/" + asset.p // If the image exists if fileExists(imagePath): // Read image as base64 imageBase64 = readFile(imagePath, asBase64) // Find out file type extension = getFileExtension(asset.p) mimeType = (extension in ["jpg", "jpeg"]) ? "image/jpeg" : "image/png" // Replace asset path with base64 data URI asset.u = "" asset.p = "data:" + mimeType + ";base64," + imageBase64 asset.e = 1 // marking it as embedded return stringify(animationJson) function embedImagesInLottieJSON(animationJsonPath, imagesFolderPath): // Read the animation JSON as text animationJsonText = readFile(animationJsonPath, asText) animationJson = parse(animationJsonText) // If there are no assets, just return original JSON if animationJson.assets is not an array: return animationJsonText for each asset in animationJson.assets: if asset.u exists AND asset.p exists AND "images" in asset.u AND asset.e == 0: // Get the image file path imagePath = imagesFolderPath + "/" + asset.p // If the image exists if fileExists(imagePath): // Read image as base64 imageBase64 = readFile(imagePath, asBase64) // Find out file type extension = getFileExtension(asset.p) mimeType = (extension in ["jpg", "jpeg"]) ? "image/jpeg" : "image/png" // Replace asset path with base64 data URI asset.u = "" asset.p = "data:" + mimeType + ";base64," + imageBase64 asset.e = 1 // marking it as embedded return stringify(animationJson) All referenced image are now packed right into the json file. After this embedding, save your processed JSON to a “processed” folder for instant re-use: const processedDir = `${extractPath}/processed`; const processedDirExists = await RNFB.fs.exists(processedDir); if (!processedDirExists) { await RNFB.fs.mkdir(processedDir); } const processedJsonPath = `${extractPath}/processed/embedded.json`; await RNFB.fs.writeFile(processedJsonPath, embeddedJSON, 'utf8'); const processedDir = `${extractPath}/processed`; const processedDirExists = await RNFB.fs.exists(processedDir); if (!processedDirExists) { await RNFB.fs.mkdir(processedDir); } const processedJsonPath = `${extractPath}/processed/embedded.json`; await RNFB.fs.writeFile(processedJsonPath, embeddedJSON, 'utf8'); How Do You Know a Lottie Is Downloaded and Ready? It’s better to show the relevant screen or entry only after the Lottie is fully processed-especially if your animation is an essential part of the user flow. Before showing the animation, check if the processed JSON file (embedded.json) exists. If it does, you can play it right away. Otherwise, do the download-unzip-embed steps first. embedded.json const isLottieProcessed = async (animationName) => { const processedJsonPath = `${RNFB.fs.dirs.DocumentDir}/${animationName}_unzipped/processed/embedded.json`; return await RNFB.fs.exists(processedJsonPath); }; const isLottieProcessed = async (animationName) => { const processedJsonPath = `${RNFB.fs.dirs.DocumentDir}/${animationName}_unzipped/processed/embedded.json`; return await RNFB.fs.exists(processedJsonPath); }; Why This Approach is Better Faster experience: Animations load instantly after the first download-no repeated network or data use.Reliable offline: All assets are embedded; nothing breaks when the user is offline or has bad connectivity.Remote control: Marketing and product teams can swap out or update animations on the fly, without shipping a new app.Lean apps: Your APK or IPA size stays small and agile, even with dozens of beautiful Lottie animations. Faster experience: Animations load instantly after the first download-no repeated network or data use. Faster experience Reliable offline: All assets are embedded; nothing breaks when the user is offline or has bad connectivity. Reliable offline Remote control: Marketing and product teams can swap out or update animations on the fly, without shipping a new app. Remote control: Lean apps: Your APK or IPA size stays small and agile, even with dozens of beautiful Lottie animations. Lean apps: One small catch in this approach is when we want to update or change something in a Lottie animation-we usually end up saving it with a new name. That starts the whole download, unzip, and embed process again, which can quickly lead to duplicate animations piling up on the device, all with slightly different content or versions. The simple fix here is to use versioning in our filenames-like naming our file first-v1.lottie, first-v2.lottie etc. This way, whenever we make any changes, just bump the version in the file name. Then, in our extract or caching logic, we can easily clear out any older versions and keep only the latest one on the device. This saves the storage and makes sure users always get the right, up-to-date animation without unnecessary duplicates. first-v1.lottie first-v2.lottie If you haven’t tried out .lottie yet, you’re probably missing out on some easy wins, this approach might be worth exploring. .lottie