← All posts

Building apps that keep working on unreliable connections

How to design mobile and web apps that survive slow networks, dropped connections and power cuts: local caching, queued actions, safe retries, sync conflicts and honest UI states.

Author
Elie Chaaban
Published
Reading time
5 min
Topics
Mobile Apps, Architecture, React Native

Most apps are designed and tested on fast office Wi-Fi. Real users are elsewhere: on mobile data in a car, in a basement, in a building where the router just lost power, in a village with one bar of signal. In many places, connections that drop or slow to a crawl are a normal part of the day, not an edge case.

An app that assumes a perfect network fails in frustrating ways: spinners that never end, forms that lose what you typed, actions that might or might not have happened. An app designed for unreliable connections stays useful and, just as important, stays honest about what has been saved.

This matters most for field apps. The technician app I built for NeonTask is used for jobs, check-ins, notes and photos on site, where connectivity is whatever the building provides. These are the patterns I use.

Decide what must work offline

Full offline support for everything is expensive. Decide per feature:

This list drives the architecture. Write it down with the product owner before building.

Cache what users need to read

The app should show the last known data immediately, then refresh in the background. In React Native, libraries like TanStack Query with a persisted cache, or a local database such as SQLite, make this straightforward.

For a field app, prefetch what the user will need: when the technician opens the app in the morning on good Wi-Fi, download today's jobs with their addresses, notes and any reference files. Show when the data was last updated, so users know if they are looking at something stale.

Queue what users write

The key pattern for writes is an outbox: every action the user takes (a note, a status change, a photo) is saved locally first and added to a queue. A background process sends queued items to the server when the connection allows, in order, and marks them done when the server confirms.

From the user's point of view, the action happened immediately. The UI shows it as "saved on device, syncing" until the server confirms, then as synced.

Without this, every write is a gamble: the user taps "Complete job", the request hangs, they tap again, and now you have either nothing or duplicates.

Make retries safe with idempotency

Networks fail in the worst place: after the server has processed a request but before the response reaches the phone. The app thinks it failed and retries, and the server processes it twice.

The fix is idempotency. The app generates a unique ID for each action when it is created, and sends it with the request. The server stores processed IDs and, if the same ID arrives again, returns the original result instead of repeating the action. This makes retries safe, and it is essential for anything involving money, bookings or status changes.

Pair it with sensible retry logic: exponential backoff, a cap, and retrying when the device reports connectivity has returned.

Handle photos and large uploads separately

Photos are where field apps struggle most. A few high-resolution images can be several megabytes each, and a weak connection cannot push them through in one request.

Resolve conflicts deliberately

If two people can edit the same record, offline edits will eventually conflict. Decide the rule per type of data:

Tell the user when a queued action was rejected, and why. Silently dropping it is the worst outcome.

Design honest UI states

Users trust an app that tells them the truth:

Web apps too

The same thinking applies to web apps used in the field or on weak connections. Keep page weights small, cache static assets aggressively, save form drafts in local storage, and use the same idempotent request pattern. Service workers can provide real offline support for web apps when needed, though for heavy offline use a native or React Native app is usually the better tool.

The server side

Offline tolerance is not only an app concern. The backend should:

The API for NeonTask, for example, is a .NET Core service that handles work orders, technicians and their updates. Designing it with sync in mind from the start is much cheaper than retrofitting it. See API design for a mobile backend.

Test with a bad network on purpose

Use the network throttling tools in iOS and Android developer settings, or a proxy, to simulate slow and lossy connections. Test these scenarios explicitly:

If the app survives these, it will survive a normal day in the field.

Is it worth it?

For an app used indoors on stable Wi-Fi, basic caching and good error messages may be enough. For apps used on the move or on site, offline tolerance is the difference between an app staff rely on and one they work around. Plan it from the start; it shapes the data model and the API.

If you are building an app for field staff or for customers on the move, see my work and get in touch. The trade-offs between platforms are covered in React Native vs native.

More from the blog

Let's work
together