Context
Orbit's mobile web experience scored well on every audit we ran and badly in every conversation we had with users. "It feels like a website" was the note, repeated in different words by nine of twelve interviewees. That is not a bug report, and for a while we treated it as unactionable.
Approach
Name the specific feelings
I recorded slow-motion screen captures of Orbit next to three native apps and catalogued each difference. Three patterns explained almost all of it: taps had no immediate acknowledgement, transitions cut rather than moved, and scrolling gave up the position when you came back to a list.
None of those appear in a Lighthouse report.
Acknowledge every touch within one frame
Every interactive surface got a pressed state that fires on pointerdown,
before any network work begins. The visual cost is a two-percent scale and an
opacity shift; the perceptual cost is zero waiting.
<motion.button
whileTap={{ scale: 0.97 }}
transition={{ type: "spring", stiffness: 420, damping: 38 }}
/>Move things instead of replacing them
List-to-detail navigation became a shared-element transition: the tapped card's
image and title travel into the header rather than disappearing and reappearing.
The work is one layoutId per element, and it is the single change users
mentioned most.
Then fix the actual performance
With the perceptual work done, the real numbers mattered again. Route-level code splitting, a smaller icon strategy and moving the heaviest list to virtualised rendering brought interaction-to-next-paint from 310ms to about 90ms.
Outcome
Mobile sessions got roughly two and a half times longer. In the follow-up round, eleven of twelve users described Orbit as an app without being prompted, which was the actual goal the whole time.
What I would do differently
I would do the perceptual audit first and the performance work second, which is the order we eventually arrived at by accident. Two weeks of early optimisation went into a bundle-size win that no user could feel.