The report
We build a canvas-based drawing tool for schools, embedded inside our main mobile app, the app educators, students, and families use day to day. By December 2024, that meant one specific place things could go wrong: a WebView inside the mobile app, loading a React web app inside it.
It was the middle of exam season when the first bug report came in: a few students couldn’t load the web canvas based drawing/sketching app. It showed an infinite loader.
The reports were coming from one school in the Middle East, a lot of students, right as they were trying to submit mid-semester work. And whatever was causing it, we couldn’t reproduce it ourselves.
Why preloading exists
A quick bit of context: loading a web app inside a WebView isn’t free. Every time it happens, the device has to fetch the JS, CSS, and HTML from scratch and it is slow, especially on a mobile connection. So like a lot of apps doing this, we used a common trick: preloading.
The idea is simple. The moment the mobile app starts, we spin up a WebView in the background. Invisible, display: none, and start loading the canvas app before the student ever asks for it. By the time they actually open it, it’s already there, warm and ready.
It’s a good trick, and most of the time, it worked exactly as intended.
Ruling out the obvious
Our first move was the standard one. We assumed it was a firewall issue. Schools often block URLs they don’t recognize, so we asked them to whitelist ours. It’s usually a quick fix. This time, it wasn’t. The bug didn’t budge.
We tried reproducing it ourselves normal network, slow network, nothing. The canvas app loaded fine every time. We turned to Browserstack, picked iPads from the same region, throttled the connection to match, and finally got a partial repro. The app did load slowly. But it loaded. Two to three minutes, not forever.
That wasn’t the bug we were chasing. Students weren’t waiting three minutes, they were stuck indefinitely.
We sent someone from the team on-site. Same result: stuck. Not a firewall, not our test environment, not a fluke. Whatever this was, it was real, and it was only real for them.
Chasing the region
With the obvious explanation gone, we started reaching for regional ones.
We also suspected network interception, heavy-handed filtering at a national level, common enough in some parts of the world. We ruled out that possibility since the pattern didn’t match, and switching the students to mobile data instead of school wifi made no difference.
Someone else wondered if it was just too many students on the same network at once. Again no, the bug hit students independently, not in a pattern that tracked with shared network connection.
Middle Eastern mobile data can be expensive, and expensive sometimes means throttled. But the school sent us a speedtest which showed a steady 20-25 Mbps. Not the problem.
The call with the affected school
We added Sentry logs to track where the load was getting stuck. It wasn’t consistent, sometimes stuck in one part of the React app, sometimes another. We added more logging inside componentDidMount (the codebase predates functional components).
We got on a call with the school. The teacher used a student’s actual device while we watched logs come in live. Clear data, but no answer yet. The load kept stalling at different points each time, somewhere in the list of internal tasks we run to fetch student data.
Then a technical manager on the call, not someone actively working on the project, asked us to disable preloading via a feature flag. Load time dropped from 2-3 minutes to 15 seconds.
That stung a little. The fix was one flag, and it took someone outside the project to think of it.
A fix that explained nothing
We disabled preloading in mobile WebViews globally for our mobile apps, via migration. Load times increased slightly under normal conditions, but the app stopped hanging indefinitely.
It wasn’t the fix that explained anything. We spent months chasing why preloading caused this in the first place:
-
WKWebViewdeprioritizing background processes. But the logs were coming in fast enough that the WebView clearly wasn’t starved of CPU. - Bundle size: The canvas app is 3-5MB after extraction. Possible, but we didn’t have hard data connecting this to the failure.
- Regional network monitoring: Since this was before the war, so no infrastructure disruption to point to.
Disabling preloading hadn’t even fully fixed the underlying slowness, some users still saw load times up to a minute on the normal, on-demand flow. Preloading wasn’t the root cause. It just turned a slow load into an infinite one.
The real fix, discovered by accident
The real answer showed up months later, indirectly.
Around the middle of the year, my team raised the min_ios_version_supported for our apps. This meant users on older devices either had to update or lose access. We didn’t do this to fix the loading bug, it wasn’t even the primary reason. But in the months after, the infinite-loader reports stopped. They just didn’t come back.
We hadn’t proven this was the fix. We’d removed the population of devices most likely to be affected, and the symptom disappeared with them.
What I think was actually happening
Here’s what I think was actually happening. Older iPads (8th–10th gen) didn’t have the CPU or memory headroom to load the canvas app fast enough, especially while a background WebView was also being preloaded, competing for the same limited resources. On a fast device, that race condition resolves in milliseconds and nobody notices. On a slow device, it can stall indefinitely.
That also explains why webViewWebContentProcessDidTerminate never fired, the WebView wasn’t crashing. It was just starved, still technically alive, never finishing.
Preloading wasn’t the root cause. It just turned an existing problem, old hardware struggling with a 3-5MB app into a worse one. The real fix wasn’t a code change. It was deciding which devices we’d support.
And the regional pattern we chased for weeks? Probably a coincidence of reporting, not geography. We only ever found what people told us about.


Top comments (0)