As Fast As It Feels
Real speed and perceived speed are the same thing. If it feels slow, it is slow.

If you are unfortunate enough to be caught in a traffic jam, knowing the reason why and getting regular updates makes the wait bearable. The journey takes just as long, but perception changes everything.
Applications face the same challenge. They carry dynamic content, live updates, and potentially time-sensitive transactions. All of it is expected to fit in a pocket, open instantly, and feel polished from the first touch.
The delivery mechanism is irrelevant. Experiences must feel premium across multi-channel contexts, as explored in Beyond the WebView. A poor experience is poor everywhere. Players notice stutters, blanks, and resets. They also notice when every action responds immediately, every transition carries context, and every session restores seamlessly.
Speed is not split into “real” and “perceived.” They are the same experience. If an app feels slow, it is slow. If it feels fast, it is fast. Metrics and perception collapse into one.
The good news is that nothing stands in the way. Modern web capabilities make these experiences entirely possible today. View Transitions, GPU-driven motion, and edge caching are widely supported. The question is no longer can it be done, but whether product engineering applies the patterns consistently.
Principles of fast
Three principles change everything.
The first is to optimise and anticipate. The app should feel like it has loaded instantly, which is what SSR and selective hydration are built for, as explored in A Level Start. Real polish comes from anticipating what happens next. Prefetching and prerendering keep the following screen warm so navigation feels instant. This is what LCP, the Largest Contentful Paint metric, is intended to reflect.
The second is to respond immediately. Every tap must trigger feedback within 100ms, whether that is a pressed state, a colour change, or a haptic tick. Without it, the player hesitates, wondering if their command was received. This is what INP, Interaction to Next Paint, measures directly.
The third is to stabilise the layout. If text or images jump while loading, continuity is broken. Reserving space for assets is not a polish task, it is the foundation of speed. This is exactly what CLS, Cumulative Layout Shift, is designed to catch.
Other principles support these three. Cache wherever possible, both at the CDN and in service workers, and treat anything that cannot be cached as a product decision rather than a default. Carry context forward with the View Transitions API. Keep all motion off the main thread using GPU transforms and opacity.
At 60 frames per second, you only have about 16ms to do your work. Any JavaScript that interferes with rendering risks dropping frames and breaking the sense of smoothness. Libraries like Lottie are especially heavy because they run animations on the main thread. If most of the frame budget is spent there, there is little room left for input handling or rendering. The result is visible stutter. Make sure critical assets load first, and break long tasks into smaller ones so they never block responsiveness.
What to do with waiting
Waiting comes in flavours, and each needs a different response. The mistake is treating them all alike.
For interactions under 100ms, the player should see and feel acknowledgement straight away.
For waits up to a second, optimism wins. Show the change immediately, even if the network is still resolving.
When delays stretch beyond a second, structure matters. SSR should give you a fast initial load, then a View Transition into a skeleton layout that mirrors the final page. It is always better to navigate to the next context instantly and show a skeleton than to hold the player in place behind a spinner. A skeleton says "you are here, and the details are on their way." A spinner says only "you are stuck, wait until we decide to move." One preserves momentum, the other arrests it.
Longer waits, three to ten seconds, need honest progress. Reveal content in stages, or show a progress bar that maps to real work. And anything beyond ten seconds must be treated with respect. A clear progress bar, ETA, or staged steps is essential. Offer an escape route, such as cancel or back. Silence is not an option.
Things that slow you down
Some patterns linger long past their usefulness. They make even fast systems feel fragile.
The worst is the centre-screen spinner. Blocking the interface with a wheel tells the player only that time is passing, not what is happening. It halts navigation when the better choice is to move forward instantly and show the structure, even if the details are still arriving.
Close behind are flashes of white. A moment of blankness between screens breaks continuity completely. SSR, skeletons, and the View Transitions API exist so players never see nothing.
Then there are layout-shift hacks. Dropping in skeletons to cover unstable layouts is a bandage on a broken system. The only real fix is to reserve space and load predictably.
And finally, the repeat offenders: JavaScript animation libraries that run on the main thread. Lottie is the most obvious, but GSAP, Anime.js, and scroll hijackers like Locomotive Scroll or ScrollMagic make the same mistake. They promise polish, but compete with rendering and input, dragging down responsiveness. For premium apps, GPU-driven CSS transforms and the View Transitions API are the safer path.
Other mistakes worth cutting include placeholders that loop endlessly, shimmer effects that fake activity, and transitions stretched for style rather than clarity. These do not speed anything up. They only mask delay while eroding trust.
Boundaries that keep you fast
Guardrails are the boundaries that protect speed when choices get messy. Two stand above the rest. Flashes of white must be designed out completely. Sessions must restore, not reset. If a player foregrounds the app, it should be waiting where they left it, not restarting from scratch.
Other boundaries matter too: fix layout shift at the source, keep motion off the main thread, preserve navigation context like scroll and filters, anchor scrolling so async content never jumps, be honest about long work with structured progress, measure in the field with INP, LCP, CLS, and RUM, align with accessibility settings, and keep behaviour consistent across flows.
Continuity is memory
Performance is also about memory. If the system forgets, the player pays in wasted effort.
The most obvious case is scroll position. Going forward and back should return you to the same spot. Resetting to the top feels like being thrown out of context.
The same applies to forms and filters. Input and selections should remain unless explicitly reset. Inline expansions should stay open, and scroll anchoring should keep the viewport stable as new rows arrive. Above all, sessions should restore on foregrounding. Refresh what is stale, but do not wipe the state. A premium app should feel like it has been waiting, not starting over.
Motion plays a role here too. If a screen slides in from the left, the natural gesture is to slide it back to the right. If that gesture is missing, the motion is misleading. Animation and interaction must align.
Where trust breaks first
Some flows can absorb delay. Login and payments cannot. They are judged with the least forgiveness.
Login is the handshake. The form must appear immediately, not after a pause or a flash of white. Once submitted, the button must answer instantly with pressed state and haptic tick. And when login completes, the player should land back where they were, not at the front door.
Payments are when money changes hands. Layout must be stable from the outset, so totals and fields do not jump. Actions like “Deposit” must acknowledge immediately, even if the network takes longer. And because payment gateways are rarely instant, structured progress is essential: Verifying, Authorising, Complete. If the flow is interrupted by backgrounding, the player must return to the same point, not restart.
How time is told
Speed is not only mechanical, it is also narrative. The way you frame and pace a wait changes how it is felt.
The first lever is framing the pause. Microcopy such as “Checking availability” reframes a wait as progress. Skeletons that mirror real data make reveals feel aligned. Carrying a colour, icon, or heading across states ties the story together.
The second lever is bending time. A micro-delay of 150ms before a screen swap can make the transition legible instead of abrupt. Progress bars that move steadily feel faster than jerky jumps. High-stakes actions like payments sometimes benefit from a slightly elongated acknowledgement, signalling care without dragging.
And the final lever is owning the stall. Networks stall, gateways pause, services hiccup. Pretending otherwise makes players assume failure. Better to signal “This is taking longer than expected,” offer a cancel or retry, or escalate gracefully from skeleton to progress bar to explanatory text. Awareness preserves confidence.
Micro-interactions play their part too. Buttons that depress, toggles that flip instantly, accordions that expand crisply, cards that follow the finger, and pull-to-refresh that rebounds smoothly. These details are not decoration. They are the rhythm of responsiveness.
Anticipation and idle preloading buy real speed, as explored in Lazy Loading Isn’t Just Lazy It’s Late. Framing and shaping ensure that speed lands in a way players can follow and trust.
How product engineering makes it real
These patterns are not new. Most have been tried, tested, and proven across the industry. The difficulty is not in inventing them, but in applying them consistently as part of product engineering.
Designers must frame motion and states with discipline, not flourish. The choice between a spinner or a skeleton is not decoration, but an expression of trust.
Those who craft CSS must carry the weight of responsiveness. Every pressed state, every transition, every animation needs to run on the GPU and respect reduced-motion settings. Consistency here is as important as colour or typography.
Engineers must treat continuity as part of correctness. Preserving scroll, restoring sessions, anchoring layout are not extras, but part of what it means for the system to work.
Leadership must connect these threads. Performance principles should sit in design systems, component libraries, and engineering guidelines, not as tribal knowledge. Tooling, linting, and review processes should enforce them the way accessibility and security are enforced. As I argued in Trunk Is the Team, orchestration and discipline are cultural before they are technical.
Premium is not achieved by heroics. It is achieved by ensuring the patterns for speed are known, accessible, and non-optional. When every part of product engineering holds up its side, the result is an application that feels fast because it is fast.
As fast as it feels
Real speed and perceived speed are the same thing. The job is not to decorate slowness, but to remove it, then carry attention forward with continuity.
SSR gives an instant first view. The View Transitions API keeps context flowing. Haptics confirm input was heard. Skeletons and progress indicators set honest expectations. Remembering scroll, filters, gestures, and sessions prevents wasted effort. Micro-interactions carry responsiveness into the smallest touches. Framing and shaping make time comprehensible rather than empty.
These are not illusions, and they are not future promises. The modern web already provides the machinery: SSR, View Transitions, CSS transforms, service workers, and caching strategies that scale. When teams use them with intent, the result is an application that feels fast because it is fast.
Postscript
With only 16ms available for every frame, can we really afford client-side tracking, real user monitoring, session-replay tools, and the weight of third parties on top? The answer is rarely straightforward, but the trade-offs deserve as much scrutiny as the animations and transitions themselves. As explored in Watching the Watchers, the systems we add to measure experience can just as easily become the reason it slows down.