How I Reduced Firebase Loading Time from 5 Seconds to Under 100ms
Source: Dev.to
The Problem
When Muslifie launched, tour listing pages were taking 2–5 seconds to load. Users on slower connections in Pakistan, Egypt, and Indonesia were bouncing before the data even appeared.
The culprit? Every time a user opened the app, Firebase ran fresh Firestore queries — no caching, no batching, just raw reads on every open.
At 200+ guide profiles and growing, this was getting expensive and slow.
My first instinct was client‑side caching with Flutter’s built‑in Firestore persistence. It helped a little, but:
- First load was still slow
- Data was stale on reinstall
- No control over cache invalidation
Not good enough for a marketplace where guide availability changes daily.
The Solution
Move the heavy lifting to Firebase Cloud Functions with in‑memory caching.
// functions/index.js
let cachedData = null;
let cacheTime = null;
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
exports.getTours = functions.https.onCall(async (data, context) => {
const now = Date.now();
// Return cache if still fresh
if (cachedData && (now - cacheTime) ({ id: doc.id, ...doc.data() }));
cacheTime = now;
return cachedData;
});Key insight: Cloud Function instances stay warm between calls, so the cache lives in memory as long as the instance is alive — typically 15–30 minutes.
The first user to hit the function after a cold start waits ~400 ms. Every subsequent user gets cached data in under 100 ms.
Metrics
| Metric | Before | After |
|---|---|---|
| Initial load | 2–5 seconds | Under 100 ms |
| Firestore reads/day | 40,000+ | ~800 |
| Firebase bill | Rising fast | Flat |
Perceived Performance
The performance fix only works if users don’t stare at a blank screen during that first cold start. I paired it with skeleton loaders in Flutter:
// lib/widgets/tour_list.dart
Widget build(BuildContext context) {
return FutureBuilder>(
future: fetchTours(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return TourSkeletonList(); // Show skeleton while loading
}
return TourList(tours: snapshot.data!);
},
);
}Perceived performance matters as much as actual performance. Users tolerate loading if they can see something happening.
Future Improvements
If I were building Muslifie today, I’d add:
- Redis caching via Firebase Extensions for multi‑instance cache sharing
- Incremental loading — show top 10 tours instantly, load the rest in the background
- Prefetch on app launch — start fetching data the moment the app opens, before the user even navigates
Takeaways
- Don’t optimize early; profile first.
- The bottleneck wasn’t the Flutter widgets or network speed—it was unnecessary Firestore reads.
- A simple 20‑line caching function can dramatically reduce reads and cost.
- Add logging to every data fetch and look at where time is actually spent; the answer is usually simpler than you think.
Originally published at buildzn.com.