A client's Cordova-based app got rejected from Apple's review process twice this year before we got it through, and the reasons were more specific than the general guidance we'd read beforehand, which tends to talk in broad terms about apps needing to feel native rather than giving concrete, checkable criteria.
The first rejection, in more detail
The first rejection was for a login screen that felt too much like a plain website rather than a native-feeling interface, the reviewer's note specifically called out the lack of any loading feedback during authentication and a page transition that visibly reloaded the whole WebView rather than transitioning smoothly. We added platform-appropriate loading indicators and navigation transitions to address it, using a Cordova plugin that provides native-style transition animations between views rather than relying on the browser's default page load behavior, which had been the actual visual tell that gave away the underlying WebView.
The second rejection, in more detail
The second was a performance concern on an older device, a list view that stuttered scrolling once populated with real data, which we fixed by trimming the DOM nodes rendered at once rather than the whole list up front. Our original implementation rendered every item in a potentially long list directly into the DOM immediately, fine on the newer devices we'd tested on, but visibly janky on the specific older iPhone model the reviewer apparently tested against. Switching to only rendering the visible portion of the list plus a small buffer, and rendering additional items as the user scrolls, brought scrolling performance back to something a reviewer, and more importantly a real user on an older device, wouldn't notice as sluggish.
What we changed about our own process afterward
Neither issue was something we'd have caught testing only on our own newer devices, both of us upgrade our own phones more often than the average user, which quietly biases our own testing toward hardware that's faster than what a meaningful slice of real users, and apparently Apple's own review team, actually use. Testing on the oldest hardware still in Apple's supported range going forward is now a fixed line item on every mobile project's timeline, not an optional nice-to-have squeezed in if time allows.
A near-third rejection we caught ourselves
On a later project, we deliberately tested the login flow and the longest list view we could construct on an old borrowed device before ever submitting to review, specifically because of what happened here, and caught a very similar stutter on a different screen before submission this time, fixing it ourselves rather than learning about it from a rejection notice a week later. That's the real payoff of the process change, not that we've become smarter about the specific first two issues, but that the general habit of testing on old hardware before submission catches whatever the next similar issue turns out to be, rather than requiring a rejection to reveal each one individually.
What we'd tell a team shipping their first hybrid app
Apple's written guidelines talk in fairly abstract terms about apps needing to feel native, avoid "web-like" interfaces, without a precise checklist of what triggers a rejection in practice, which makes the first submission feel like a bit of a guessing game. Our best concrete advice after going through this twice: test on the oldest device Apple still officially supports, add native-feeling loading and transition indicators anywhere a WebView would otherwise show its seams, and virtualize or paginate any list view rather than assuming Cordova's WebView will handle rendering hundreds of DOM nodes as gracefully as native code would. None of that is exotic, but none of it was obvious to us either, until a reviewer's rejection notice spelled it out the hard way.
The specific tooling we added to catch performance issues earlier
Beyond just testing on older hardware, we added a lightweight frame-rate overlay during development, a small on-screen indicator showing roughly how smoothly the current view is scrolling, which we can toggle on during manual testing without needing to plug the device into a computer and dig through profiling tools every single time. It's not as precise as a full profiling session, but it's fast enough to run casually throughout development rather than only right before a submission deadline, which is exactly when we most need an early warning rather than a last-minute discovery.
How we communicate review risk to clients now
We've also changed how we talk to clients about app store submission timelines, being upfront from the start of a project that a rejection, even for something we consider minor, is a realistic possibility that adds time rather than a sign that anything is fundamentally wrong with the build. Before this project, we'd quote a launch date assuming smooth approval and then have an awkward conversation if a rejection happened. Now we build a review buffer into the schedule from day one and frame Apple's review as a normal part of the process with its own timeline, similar to how we'd frame a client's own review and feedback cycle on a design mockup, rather than an unplanned delay.
A rejection reason we've since heard from other developers that we haven't hit yet
Comparing notes with a couple of other people doing hybrid app work, we've heard secondhand about rejections for apps that are perceived as too close to "just a website in a wrapper" without enough native functionality to justify existing as an app at all, a different and somewhat more subjective category of rejection than either of ours. We haven't been hit with this one yet, possibly because both of our apps use at least one genuine device API, camera access, in a way that gives them a concrete reason to exist outside a browser tab, but it's now something we think about explicitly when scoping a new hybrid app project, does this app do something a mobile website couldn't, rather than assuming "give the client an app icon" alone is enough justification for the format.
Keeping a running internal log of every rejection reason across projects
We've started keeping a shared internal document listing every app store rejection reason we've hit or heard about secondhand, along with what fixed it, treating it as a growing internal knowledge base rather than something we relearn project by project. It's short right now, four or five entries, but each one represents real hours spent figuring something out the hard way, and referring back to it before a new submission, even briefly, has already saved us from resubmitting one later project with a mistake we'd specifically documented avoiding the first time around.
The cost of each rejection in real terms
Beyond the engineering time to fix each issue, which was genuinely modest, a day or two each, the larger cost was calendar time lost to Apple's review queue itself, roughly a week each round-trip when we factor in queue time plus the days it took us to notice, prioritize, and fix each issue before resubmitting. Across two rejections that added close to three weeks to what we'd originally quoted as a two-week launch timeline, a gap significant enough that we had an uncomfortable but necessary conversation with the client about the revised date, one we're glad we had honestly rather than quietly hoping the second submission would sail through.
Reading Apple's own rejection notice more carefully than we did the first time
One small process improvement that came directly out of the second rejection: we now read the entirety of Apple's rejection notice, including sections that seem boilerplate or generic, rather than skimming for what looks like the one specific complaint. The second rejection notice included a secondary, easy-to-miss note about a missing privacy policy link in addition to the performance issue, something we'd have caught in our own testing eventually but that would have caused a third rejection if we'd addressed only the headline complaint and resubmitted without noticing the secondary one buried further down in the same notice. It's a small enough process change, read the whole notice, twice if needed, before touching any code, that there's really no excuse for skipping it on a future submission, however tempting it is to jump straight to fixing the one issue that jumps out first. Cheap insurance, in other words, against a mistake we've already paid for once.