How to Decide What Belongs Offline, What Belongs Online, and What Belongs in a Queue
Simon stood in an airport with a boarding pass already sitting on his phone, and the flight app refused to open it. The screen said there was no connection. That ticket had been downloaded days earlier, so the data was right there on the device, and a connection check was parked in front of it like a locked door. He ended up proving he had paid and walking away with a ticket written out by hand.
A boarding pass already sits on the device, so a connection check should never hide it from the user.
It is a small story with a large lesson folded inside it. Flights, trains, hotels and festival entries are the moments where people are already tense, tired and often holding a bag in one hand. The product chose that moment to stop working, for a reason that had nothing to do with the information the user needed.
So this piece is about three piles. What belongs on the device, what has to come fresh from a server, and what can sit quietly in a queue until the signal returns. Feature lists sort screens by what they do. Sorting them by where the person will be standing gives much better answers, and it changes what you build first.
Mapping the Journey by Connectivity, Not by Feature List
On a travel product we worked on for younger backpackers heading to off-grid places, patchy signal was the normal state of the user. So we rethought the product around four questions instead of a feature backlog. What can we store offline, what has to stay online, how do we queue actions taken offline and replay them later, and how do we shrink the data moving between app and server.
The first pass is a map of stress. Check-in, finding the way, pulling up a booking reference, getting a ticket on screen. Those are the points where a person is already under pressure, and a technical failure lands much harder there than it does on a settings page.
The second pass is a dependency test, done feature by feature. Does this need a round trip to the server, or can it run from data already held? How often does that data actually change?
| Feature | Needs the server? | Rate of change | Offline treatment |
|---|---|---|---|
| Saved tickets | No | Almost never | Held on device |
| Itinerary | No | Rarely | Held on device |
| Maps for saved areas | No, once downloaded | Slow | Pre-downloaded |
| Live availability | Yes | Constant | Plain message, no stale copy |
| Payment | Yes | Not applicable | Fails clearly or queues |
Deciding What Gets Stored on the Device
The rule we used on that backpacker product was simple. Anything we could bake into the build, we baked in. Everything else we kept as light as we possibly could, so that a weak signal still had a chance of finishing the job.
Passive consumption is the easiest call to make. If the user is reading something rather than changing something, it belongs on the device by default.
- Itineraries and trip timings
- Saved tickets and booking references
- Maps for areas the user has already shown interest in
- Reference content that barely changes from month to month
- Anything the user explicitly saved or starred
Storage costs almost nothing compared with the cost of a person standing at a gate with a blank screen. The harder part is deciding how long a cached copy stays trustworthy, and that varies by data type rather than by app.
Write the cache rule next to each piece of data, not in a separate technical document. "Tickets never expire, availability expires in five minutes" is a product decision as much as an engineering one.
We also keep a plain record of when each item was last refreshed. It costs a single timestamp, and it makes every later decision about staleness, display and sync far easier to reason about.
UX/UI design built around real psychology
We design app interfaces around how people actually think and behave. User research, psychology-driven UX/UI design and technical specs delivered as one complete package.
Deciding What Genuinely Has to Stay Live
Some things have to come from the server, and pretending otherwise causes worse harm than a clear error message. Payment is the obvious one. Live availability, seat counts, prices and anything legally binding sit in the same group, because a stale copy of those numbers sends someone to a place with a promise that is no longer true.
The test we use is to ask what happens if the cached answer is wrong by an hour. For an itinerary, nothing happens. For the last two beds in a hostel, quite a lot happens.
If a stale answer causes real harm, the feature stays live and says so plainly when it cannot run.
Live does not have to mean fragile, though. A booking screen can still show the saved details, the dates, the names and the price last seen, while the confirm button explains that it needs a connection. The person keeps their orientation and loses only the one action they cannot complete.
That is the difference between a feature that depends on the network and a whole product that depends on the network. The first is reasonable. The second is what turns a short walk through a valley into a lost afternoon.
Queuing Offline Actions and Replaying Them Safely
On the backpacker product, one of the four questions we set ourselves was how to queue actions taken offline and run them back once a connection returned. Queuing is the most expensive of the three approaches to build properly, so it is worth the cost only where the action genuinely cannot wait for the user to find signal.
- Write the action to local storage before showing any confirmation.
- Show it in the interface as pending, using plain words.
- Send it when the signal returns, oldest first.
- Give every action its own ID, so a repeat send never creates a second booking.
- Expire anything too old to be safe, and say why it expired.
Shared actions need extra care. On a group booking flow, we built a way to spread data entry across everyone on the trip, so each traveller fills in their own details rather than one person typing in five passport numbers. That works nicely for friends and colleagues. For a family, where a parent is entering children's details, manual entry stays right there as the main path.
Treat collaborative or queued entry as an opt-in extra. The person who wants to do everything themselves, in one sitting, should never be blocked by a clever sync feature.
Keeping Payloads Small When Bandwidth Is Scarce
Small payloads do two jobs at once. They finish on a weak signal, and they cost the user less money. Data plans in some countries run to upwards of 10% of a typical monthly income, according to Google Design, which reframes a heavy image carousel as something closer to a bill.
On serverless social networking products, we dealt with cold-start delays by splitting the data into two levels. The first request pulls only the structure of the feed, so what posts exist and what type each one is, in a very light payload. Then a set of asynchronous calls fetches the actual content behind it.
The screen fills with real structure almost straight away, and the heavier content lands as it arrives. The same split works for a trip screen, a product grid or a booking list.
Trimming What You Send
Most of the weight in a response is data nobody is looking at. Ask each endpoint what the screen actually renders, then stop sending the rest. Images are the other half of the problem, so size them for the device rather than the design file, and hold the full version back until someone taps.
Telling Users What They're Looking At
Google Maps and Apple Maps are the clearest reference points here. They signpost the mode, so you always know whether you are looking at live data or a saved copy. They prompt you to download an area before you lose signal, and they quietly cache the places you visit often, without asking you to think about it.
The labelling matters more than people expect. A person reading a cached itinerary is perfectly happy, as long as the product says the itinerary is cached and tells them when it was last updated. The same screen with no label creates a small, nagging doubt, and doubt is what pushes someone to refresh over and over on a bar of signal.
Three Labels Worth Having
A mode indicator for the whole app, a freshness stamp on individual data, and a status on any action waiting to send. Those three cover nearly every question a user has when the connection drops.
Compare that with the flight app Simon was fighting at the airport. It knew it had no connection, it knew it had a downloaded ticket, and it chose to tell him only the first of those two facts. The information was there and the signposting was not.
Degrading Gracefully Instead of Failing Silently
Poor offline design looks like a blank screen with a spinner on it. No error state, no cached fallback, no explanation, just a person staring at a screen wondering why nothing is showing. Sometimes there is not even a spinner.
Maps handle the same situation by degrading in layers. Zoom in to street level on an area you never downloaded and the fine detail is missing, which is fair enough. Zoom out and you still get a usable overview, so you always know roughly where you are.
A screen that shows less detail is still useful, while a screen that shows nothing at all is just worrying.
Network trouble also shows up as outright crashes. Appwrk reports that around a fifth of mobile app crashes sit alongside network problems like unstable connections and server timeouts, which suggests a lot of apps treat a failed request as something unexpected rather than something to plan for.
Planning for it is mostly about defaults. Every screen gets an answer for the empty case, the stale case and the failed case, written before the happy path is signed off.
Retrofitting Offline Support Into a Live Product
We worked with a travel app used for exotic holidays and backpacking in the wilderness, and the offline problem arrived when the product expanded into a new type of trip. The app needed an internet connection for map search, saved pins, tickets, itineraries and location times. In a remote spot with no signal, opening it produced a "not connected" error and nothing else, which for somebody out in the field is an absolute disaster.
Retrofitting is a sequence, and the order saves money. First, make the app open and show something sensible with no connection at all. That one change takes it from useless to usable.
Second, cache the reads. Tickets, itineraries, saved pins and location times are static or slow-moving, so they can live on the device and refresh when there is signal.
Third, and only if the use case demands it, add queuing for the actions people take while offline. That is the expensive part, so it comes last and it comes with evidence behind it.
Each step ships on its own and each one is testable. Teams that try to do all three at once tend to stall, and the users stay stuck on the "not connected" screen the whole time.
Timing What You Show, Not Just What You Store
Storage decisions and timing decisions are the same decision wearing different clothes. We saw this clearly on a concierge app we built for residents moving into a new block of flats, mostly high-net-worth buyers. We had recycling locations, emergency procedures and local area guides, and we deliberately chose not to put all of it in front of people on day one.
Someone who has just moved is in a very mixed emotional state. Some residents had bought their first property. Others were moving after a separation or a divorce, and nobody in either group is mentally receptive to a wall of building information.
So we drip-fed it. Recycling details arrived a couple of days after move-in. Local recommendations landed over the first weekend, when people had time to wander. The product felt more human and it took weight off the user instead of adding to it.
Applied to connectivity, the thinking is the same. Hold content until the moment it is needed, and pre-load it just before the person reaches the place where signal disappears. A trip app that downloads tomorrow's leg tonight, on hotel wifi, has solved most of its offline problem through timing alone.
Knowing When Offline Isn't Worth Building
Two things decide this. The first is the use case and where the product is used. A travel app that books city hotels in well-connected places has a much weaker case than one built for remote safaris, and pretending otherwise burns budget. The second is the level of support actually needed, which runs from basic cached reads all the way up to queuing booking changes for later sync. Few products need to go as far as that top tier.
The real risk is building offline support at the cost of everything else. A small set of features done really well beats a long list that includes offline in a thin, lifeless way. People who have a bad first run with an app mostly do not come back, so the quality of what you ship matters more than the length of the list.
Saying No to the Wrong Architecture
We saw the same logic on a holistic mental health and wellness product covering mental health, eating habits, exercise and wellness. Serverless looked attractive at first. The way those endpoints would be called was too uneven to keep them warm, so cold-start delays were going to hurt. We built a monolithic service with message queues and a few separate services instead, and it served that product far better.
Testing Offline Behaviour Before Your Users Do
Offline bugs rarely show up in the office, because the office has wifi. They show up in a valley, in a basement, or in an airport where ten thousand phones are fighting over one mast. So the testing has to be deliberate rather than accidental.
- Switch to aeroplane mode and open the app completely cold.
- Walk the stress moments in order, so ticket, itinerary, map and check-in.
- Throttle the connection to something slow and flaky rather than switching it off.
- Cut the signal halfway through a booking and watch what the queue does.
- Come back online an hour later and confirm nothing sent twice.
The half-connected state deserves the most attention. A request that hangs for forty seconds and then fails silently is worse for the user than an instant, honest message, and it is the state least likely to be covered in a test plan.
Put aeroplane mode into your release checklist as a named step with a named owner. Treating it as something QA will probably get to is how a "not connected" screen reaches production.
Conclusion
Three piles, then. Things the device holds, things the server owns, and things that wait in a queue. Sorting your product that way takes an afternoon with a whiteboard, and it answers questions that a feature list cannot.
The backpacker product taught us to ask the four questions early, before the architecture hardened. The wilderness app taught us that retrofitting works when you do it in order, starting with simply letting the app open. The concierge app taught us that timing what you show is part of the same craft as deciding what you store. And a boarding pass locked behind a connection check taught us the cost of getting it wrong at exactly the wrong moment.
None of this asks for a huge budget. It asks for a decision per feature, written down, about what happens when the signal goes. Most of those decisions cost very little to act on, and the ones that cost more are easy to spot and easy to defer.
If you are weighing up how far your product should go, and which parts of your journey are worth protecting first, let's talk about your offline experience.
Frequently Asked Questions
It means sorting every screen by where the user will physically be standing when they need it, not by what the feature does. You ask whether each piece of data must come fresh from a server, can live on the device, or can wait in a queue, which usually changes what you decide to build first.
Run a dependency test feature by feature and ask two questions. Does this need a round trip to the server, and how often does the underlying data actually change? Anything that is rarely updated and only read rather than edited belongs on the device by default.
Itineraries, trip timings, saved tickets and booking references, maps for areas the user has already shown interest in, and reference content that barely shifts month to month. Anything the user has explicitly saved or starred should also be held locally.
The ticket had already been downloaded, so the data was sitting on the device, but a connection check was placed in front of it like a locked door. A connectivity test should never block access to information the user already holds.
Show a plain, honest message rather than a stale copy that might mislead the user. Live availability and payment are good examples, as a wrong answer there is worse than no answer at all.
Storage costs very little compared with the cost of a person standing at a gate staring at a blank screen. The genuinely hard part is not the space used, it is deciding how long a cached copy stays trustworthy.
Write the cache rule next to each piece of data rather than burying it in a separate technical document. Something like "tickets never expire, availability expires in five minutes" is a product decision as much as an engineering one, and it varies by data type rather than by app.
Actions the user takes while offline that need to reach the server eventually, such as changes or submissions, can be stored and replayed once the signal returns. The user gets to finish the task in the moment, and the system catches up quietly afterwards.
Start with a map of stress, meaning the points where users are already under pressure, such as check-in, wayfinding, pulling up a booking reference or showing a ticket. A technical failure at those moments lands far harder than the same failure on a settings page.