Why We Reverted Our Firestore Offline Migration

2026-08-04

We spent two weeks replacing a working local-first stack with Firebase Firestore. Then a day before release we reverted it.

We have a kids drawing app (Kids Create App) - the app originally used Isar on the device and a custom backend for synchronization. That works reasonably well, but it requires careful attention and it's a pretty complicated two-way sync with the cloud. In addition, Isar itself has been unsupported for a few years now, and SPM (Swift Package Manager) is now the preferred way to manage dependencies in iOS apps. But Isar lacks SPM support. There is a community version of Isar, and people tried to contribute SPM support there without luck.

So after spending a few hours chatting with various LLMs, they all suggested in a single voice to just switch to Firestore because it would handle everything seamlessly.

Firestore looked like a way to remove much of that complexity. It has an offline cache. It can synchronize when a user signs in. So the proposed model sounded straightforward:

  1. Keep signed-out work local by disabling Firestore networking.
  2. Store that work in a shared local Firestore bucket.
  3. On the first real sign-in, copy it to the user's Firestore account.
  4. Enable networking and let Firestore synchronize it.

I spent a few weeks (in between other things) implementing that migration. The code seemed to be working fine, and we were ready to release it. All tests were looking great.

Then I started doing manual testing for the fresh-install experience (before a user actually signs in).

Almost nothing worked:

  • Save buttons didn't work to save images
  • Gallery was empty
  • Crashes and hangs

The fundamental mistake: treating a sync cache as a local-first DB

Firestore's offline persistence is a sync cache, not an application database. An app database provides immediate local truth and transactional durability. A sync cache is just an optimization for a remote backend.

When we tested offline on real devices, the mismatch broke almost every core flow:

  1. Queued writes hung the UI: Awaiting a Firestore write future while networking was disabled never completed in on-device tests. The save button hung indefinitely waiting for network acknowledgement.
  2. Offline reads failed on cache misses: Queries and document reads failed or returned stale data if items weren't already cached. A local gallery can't rely on luck for whether its data is in the local cache.
  3. Firebase Storage lacks offline queuing: Larger drawing payloads had to go to Firebase Storage, which doesn't share Firestore's offline queue. Offline saves blocked waiting for storage uploads that couldn't happen.
  4. No local transaction boundary: Saving required replacing a local thumbnail file and updating metadata in Firestore. Without a local transaction, a stalled metadata write left deleted thumbnails and broken image links.
  5. Deletes failed for local-only items: Deleting local offline items via Firestore batch updates failed because the document didn't exist on the server yet.
  6. Sign-in migration deadlocked: Claiming offline drawings required writing them to users/{uid} before enabling networking. But those writes waited for remote confirmation, deadlocking the app—networking couldn't enable until writes finished, and writes couldn't finish until networking enabled.
  7. Unit tests gave false confidence: AI-generated unit tests passed control flow but completely missed real-device integration edge cases like disabled networking, cache misses, and interrupted migrations.

What we did & lessons learned

We reverted the migration and kept Isar as our local store with custom backend sync.

While Isar isn't ideal due to maintenance issues, it gives us an explicit local source of truth. If we integrate Firestore in the future, it will be as an asynchronous transport target—not the primary local persistence layer.

The takeaway: "Offline support" is not "local-first architecture." A local-first app must guarantee local durability instantly, independently of remote network state or server acknowledgement.