UX

Building PWAs for Real People

Not perfect networks.

Most web applications are built assuming reliable Wi-Fi. The developer has fast broadband. The staging environment is on a local network. Testing happens in ideal conditions. Then the app ships to users who are nowhere near ideal conditions.

The users I build for work in warehouses with concrete walls that kill signal. They're on construction sites where the only internet is a phone hotspot that drops constantly. They're in rural areas where 3G is still common and 4G is a luxury.

Building for these users changed how I think about every technical decision.

Why "Perfect Wi-Fi" Assumptions Fail

The failure modes are predictable:

  • Loading states that never resolve. A spinner that assumes the request will eventually complete. It won't. The user stares at it for 30 seconds, then closes the app.
  • Lost form data. A user fills out a detailed form. Hits submit. Network drops. The request fails. The form resets. Everything is gone.
  • Stale data presented as current. Cached data displays but there's no indication it might be outdated. The user makes decisions based on information that changed hours ago.
  • Silent failures. An action appears to succeed because the UI updated optimistically. But the sync failed. The user doesn't know until much later when the data is wrong.

These aren't edge cases. For users in challenging network environments, they're the default experience.

UX for Failure States

Every network operation can fail. The UX needs to handle that gracefully.

Timeouts with recovery. Don't wait forever. Set reasonable timeouts. When they hit, offer options: retry, work offline, or abandon. Never leave the user trapped in a loading state.

Persistent input. Any user input should survive network failures, app restarts, and browser crashes. Store form data locally as the user types. Restore it when they return. Never lose their work.

Clear sync status. Users should always know if their data is synced, pending, or failed. A subtle icon is enough — checkmark for synced, clock for pending, warning for failed. Don't hide this information.

Actionable errors. "Network error" tells the user nothing useful. "Changes saved locally. Will sync when connected." tells them exactly what happened and what to expect.

Mobile-First Realities

Mobile isn't just "smaller screens." It's fundamentally different constraints:

  • Interrupted sessions. Users get phone calls. They switch apps. They walk into elevators. Sessions are constantly interrupted. State must persist across these interruptions.
  • Variable bandwidth. A user might start on Wi-Fi, walk outside, drop to 4G, then hit a dead zone. The app needs to handle all of these within a single session.
  • Battery concerns. Aggressive polling and constant network activity drain batteries. Users notice. Batch operations where possible. Use push instead of poll where available.
  • Touch targets. Error recovery UI needs to work with fingers, not mice. Retry buttons need to be large enough to tap accurately. Error messages need to be readable on small screens.

Performance Budgets

A performance budget isn't a guideline. It's a constraint that shapes every decision.

For the users I build for, the budget is tight:

  • Initial load under 3 seconds on 3G. That means aggressive code splitting, minimal JavaScript, and smart preloading. Every kilobyte matters.
  • Time to interactive under 5 seconds. The app shell needs to render fast and become usable fast. Users won't wait for a full data fetch before they can do anything.
  • Subsequent navigations under 1 second. Once the app is loaded, navigation should feel instant. Service worker caching, prefetching, and local data make this possible.

These aren't arbitrary numbers. They're based on when real users give up and try something else.

Designing for Interruption

The assumption that users complete tasks in a single session is wrong. They get interrupted. They come back hours later. They switch devices.

Design implications:

  • Save state constantly. Don't wait for explicit save actions. Auto-save everything. Make it impossible to lose progress.
  • Support resume. When a user returns, put them back where they were. Don't make them start over or navigate back to their task.
  • Handle stale sessions. A session started yesterday might resume today. Data might have changed. Handle the reconciliation gracefully.
  • Work across devices. Users might start on their phone and finish on their laptop. Cloud sync makes this possible. Local-only storage makes it impossible.

Real Feedback Loops

The best insights come from watching real users in real conditions. Not usability labs with perfect Wi-Fi. Actual field use.

I've sat with users in warehouses, watching them struggle with apps that worked perfectly in my office. The problems are obvious when you see them. The user taps a button. Nothing happens for 10 seconds. They tap again. Now there are duplicate requests. The app state is confused.

Those observations drive better technical decisions than any amount of theoretical architecture discussion.

Build for the users you actually have, in the conditions they actually face. Everything else is optimising for a fantasy.

See Real-World Examples

Case studies from applications built for challenging network environments.

HD
Headless Digital

Senior, hands-on, and accountable. No inflated teams. No unnecessary layers.

Connect

© 2025 Headless Digital. All rights reserved.

Built withNuxt & Tailwind