Profile    Mohammed Shiroz Status   Loading  
Logo
Share This
Back to blog
Filter by:
Tags
//Article title

Making Your Web App Work Well on Slow Mobile Networks

About Post

Your app is fast. You know it's fast, because you use it every day: on office Wi-Fi, on a recent laptop, with the server a few milliseconds away.

Your users are in a lift. Or a basement car park. Or on a train between two towers, with one bar of signal and a phone that's three years old. For them, the same app is a white screen, a spinner, and a button that may or may not have done something.

Building for slow networks isn't a niche concern. It's the normal condition of mobile use, at least some of the time, for nearly everyone. Here's what makes the biggest difference, roughly in the order I'd tackle it.

First, feel the pain yourself

You won't fix what you never experience. Before changing any code, use your app on a bad connection:

  • In Chrome or Edge DevTools, the Network tab has throttling presets for slow mobile connections. Turn one on and reload your main screens.
  • Android emulators let you set network speed and latency; on iOS, Apple's Network Link Conditioner does the same on a device.
  • Even better, walk somewhere with poor signal and try the critical flows on a real phone.

It's humbling. Things you never noticed, like five requests firing one after another before a screen appears, suddenly take seconds each.

Latency hurts more than bandwidth

Here's the insight that changes how you design: on mobile, the killer is often not how many bytes you send, but how many round trips you make. Every request has to travel to the server and back, and on a weak mobile connection each trip can take a long, unpredictable time.

So a screen that needs one 40 KB response usually beats a screen that needs six 5 KB responses made one after another. Watch out for waterfalls: fetch the user, then use their ID to fetch their account, then use that to fetch the list. Each step waits for the one before it.

  • Design API endpoints around screens, so a screen can load with one or two requests.
  • Fire independent requests in parallel instead of in sequence.
  • Avoid redirects on the critical path; each one is another round trip.

Send less

Bytes still matter, especially on metered data. The usual suspects:

  • Bloated API responses. Returning every column of every related model "just in case". Use API Resources (or whatever your framework offers) to return only what the screen needs, and paginate lists.
  • Images. Usually the heaviest thing on the page. Resize to the size you display, use WebP or AVIF, and lazy-load what's below the fold.
  • JavaScript bundles. Split by route so the first screen doesn't download the code for every other screen.
  • Uncompressed text. Make sure the server compresses HTML, CSS, JS and JSON with gzip or Brotli. It's often a one-line server setting with a large effect.

Don't download the same thing twice

The fastest request is the one you never make. Static assets with hashed file names (which Vite produces by default) can be cached by the browser for a very long time, because a new build gets a new file name.

For API data that doesn't change often, conditional requests help: the server sends an ETag, the client sends it back next time, and if nothing changed, the server replies 304 Not Modified with no body. Laravel has middleware for cache headers:

Route::middleware('cache.headers:private;max_age=300;etag')
    ->get('/api/settings', SettingsController::class);

Use private for anything user-specific so shared caches don't store it, and keep max_age short for data that changes.

Make waiting feel shorter

You can't make every response instant, but you can make the app feel responsive:

  • Skeleton screens show the shape of the content while it loads. They feel faster than a spinner because the user can see what's coming.
  • Show cached data first, refresh in the background. Yesterday's list right now, updated a second later, beats an empty screen.
  • Optimistic updates for low-risk actions: mark the message as read or the item as liked immediately, and quietly roll back if the request fails. Don't do this for payments or anything the user must be sure of.
  • Immediate feedback on every tap. A button that doesn't react for two seconds gets tapped again.

The principle: the user should never wonder whether the app heard them. Acknowledge every action instantly, even if the real work takes a while.

Expect requests to fail

On a weak connection, requests time out, fail halfway, or succeed without the response ever arriving. Plan for all three:

  • Set timeouts. Without one, a request can hang far longer than any user will wait. On the web, fetch(url, { signal: AbortSignal.timeout(15000) }) gives up after 15 seconds.
  • Retry safely. Retry reads automatically with backoff. For actions that create things (a payment, a maintenance request), send an idempotency key so a retry can't create a duplicate.
  • Keep the user's input. If a form submission fails, never clear the form. Losing a long message to a dropped connection is how apps get one-star reviews.
  • Write honest error messages. "No connection. Your request is saved and will be sent when you're back online" is far better than "Something went wrong".

Offline hints: tell people what's happening

When the connection drops completely, say so with a small, calm banner, rather than letting every action fail mysteriously.

In the browser, the online and offline events and navigator.onLine help, with one catch: false reliably means offline, but true only means the device has a network, not that the internet actually works. Treat failed requests as the real signal.

In React Native, which we use for the Kate PMS tenant app, the community NetInfo library gives you a richer picture:

import NetInfo from '@react-native-community/netinfo';

const unsubscribe = NetInfo.addEventListener((state) => {
  // isInternetReachable can be null while it's still checking
  setOffline(state.isConnected === false || state.isInternetReachable === false);
});

For apps that must work fully offline, there's a further step: queuing actions locally and syncing later (on the web, with a service worker). It's powerful but adds real complexity around conflicts, so do it for the flows that genuinely need it, not by default.

The checklist

  • Test on a throttled connection and a real phone with poor signal.
  • Cut round trips: screen-shaped endpoints, parallel requests, no waterfalls.
  • Send less: lean JSON, compressed text, resized modern images, split bundles.
  • Cache aggressively and use ETags for data that rarely changes.
  • Skeletons, cached-first rendering and instant feedback on every tap.
  • Timeouts everywhere, safe retries with idempotency keys, never lose user input.
  • A calm offline banner, and real offline support only where it's needed.

Where do your users struggle with connection most: lifts, basements, trains, or somewhere more surprising? Knowing that changes which of these you fix first.

Comments (0)
Leave your review

Thanks for your valuable comments. Your comments has been updated and appreciate your getting in touch...

01. About Shiroz

Mohammed Shiroz

Hi, I'm Mohammed Shiroz, a software engineer and AI enthusiast from Sri Lanka who turns ideas into intelligent, real-world solutions. With over 9 years of hands-on experience, I currently lead real estate ERP development at Kate Group, a...

03.My Projects

04. Categories

Ready To order Your Project ?

Get in Touch
Close