Most frontend performance discussions still start with network speed. That matters, but it is not what users feel first.
A frontend feels fast when it acknowledges intent immediately, keeps the layout stable, reveals useful content early, and avoids making the user wait for the whole page to become "done" before anything responds.
That is why modern performance work is less about shaving one more millisecond off a request and more about controlling when the user gets feedback, how much of the UI becomes usable early, and whether the browser has to keep redoing expensive work.
1. Speed starts with acknowledgement
If a click lands and nothing reacts, the interface already feels slow even if the request finishes quickly. The first job of a modern frontend is to acknowledge intent inside the same interaction frame.
That acknowledgement can be:
- a pressed state on a button
- a route transition that starts immediately
- a skeleton for a content area
- an optimistic update in a list
- a progress hint that confirms work has started
This is why INP matters so much more than teams used to think. Users do not experience a page as a static document anymore. They experience it as a stream of interactions. A fast site with bad interaction latency still feels broken.
In React, this often means separating urgent work from non-urgent rendering. For example, keep the tap or click responsive first, then let heavier UI reconcile in a transition:
import { startTransition, useState } from 'react';
function SearchBox() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<string[]>([]);
const handleChange = async (value: string) => {
setQuery(value);
const nextResults = await fetchResults(value);
startTransition(() => {
setResults(nextResults);
});
};
return <input value={query} onChange={e => handleChange(e.target.value)} />;
}The important part is not React-specific. The important part is architectural: urgent feedback should not wait for expensive rendering.
2. Streaming beats waiting for "the whole page"
Older apps treated a page as complete only when all data had arrived. Modern apps should stop doing that.
A better model is:
- send the shell fast
- reveal primary content as soon as possible
- stream secondary content later
- hydrate only where interactivity is needed
This is where Server Components, streaming SSR, and suspense boundaries help. They are not magic performance buttons, but they let you structure work so that users see useful UI earlier.
The win is not only on the network. Streaming also reduces the psychological cost of waiting because the page starts progressing immediately.
export default function DashboardPage() {
return (
<>
<Header />
<Sidebar />
<Suspense fallback={<RevenueSkeleton />}>
<RevenuePanel />
</Suspense>
<Suspense fallback={<ActivitySkeleton />}>
<RecentActivity />
</Suspense>
</>
);
}The shell, navigation, and structure can appear immediately. Revenue and activity do not need to block each other. That alone changes how fast the page feels.
3. Optimistic UI is often more important than raw request time
If a mutation takes 500ms but the UI updates instantly and then confirms, the action often feels fast enough. If the same mutation takes 250ms but the UI stays frozen and uncertain, it feels worse.
That is why modern products lean on optimistic patterns for:
- likes and reactions
- checkbox toggles
- list reordering
- chat/message sending
- inline editing
The rule is simple: use optimistic updates when the success rate is high and the rollback story is clear.
const [optimisticItems, addOptimisticItem] = useOptimistic(items, (state, draft) => [
draft,
...state,
]);
async function submit(text: string) {
const optimistic = { id: crypto.randomUUID(), text, pending: true };
addOptimisticItem(optimistic);
try {
await createComment(text);
} catch {
// rollback or refetch
}
}This is one of the biggest differences between an app that is technically fast and an app that feels fast. The network is no longer the only timeline users perceive.
4. Layout stability is performance
Late content that shifts the screen destroys the feeling of speed. The browser may have loaded the pixels quickly, but if text jumps, buttons move, or images resize after render, users interpret the interface as unstable and therefore slow.
This is exactly why CLS still matters. Modern frontends need to reserve space early and render media with known dimensions.
Good defaults:
- always provide image width and height or an aspect ratio
- reserve space for ads, embeds, and charts
- avoid swapping fonts in ways that reflow large text blocks
- keep skeletons close to the final layout, not generic grey boxes
.cardMedia {
aspect-ratio: 16 / 9;
background: #17191f;
overflow: hidden;
}
.cardMedia img {
width: 100%;
height: 100%;
object-fit: cover;
}This looks simple, but it prevents a surprising amount of jank. A stable interface reads as a fast interface.
5. Media strategy matters more than teams admit
Modern pages are often bottlenecked by media behavior, not JavaScript alone.
A frontend feels fast when:
- above-the-fold media is prioritized intentionally
- below-the-fold media is lazy without hurting scroll smoothness
- decoding happens predictably
- image sizes match the real rendered size
- video thumbnails are cheap and stable
The bad version is common: a page ships "optimized images" but still downloads assets far larger than needed, triggers decode work at the wrong moment, and causes the browser to fight both painting and layout at once.
For article pages in particular, media should support reading rhythm, not interrupt it.
6. Prefetch helps, but only when it is selective
Prefetch can make navigation feel instant, but aggressive prefetching is not free.
It costs:
- bandwidth
- memory
- cache pressure
- CPU work to parse and evaluate future code
Modern apps should prefetch based on probability, not hope. Good candidates:
- links in or near the viewport
- likely next-step routes
- hover-triggered intent
- recently visited sections that are likely to be revisited
Bad candidates:
- every link in a large feed
- deep routes users rarely open
- data-heavy pages with short cache lifetimes
The goal is not "prefetch more". The goal is "make the next likely action feel instant without making the current page heavier."
7. The browser main thread is still the real bottleneck
Many teams now understand bundle size. Fewer teams think clearly about main-thread pressure after the bundle arrives.
A frontend feels slow when the browser is busy with:
- large synchronous renders
- too many layout reads and writes
- expensive effects on mount
- oversized hydration boundaries
- animation work that competes with interaction
This is why partial hydration, island architecture, Server Components, and better scheduling matter. They reduce how much JavaScript has to compete for the main thread during the moments users are actually touching the page.
At the component level, the rule is still boring and useful:
- avoid rendering what is not visible
- avoid recomputing what has not changed
- defer non-urgent work
- keep interaction paths short
8. Measure what users can feel
If I had to pick the metrics that map most directly to user perception on modern frontends, I would watch:
INPfor interaction responsivenessLCPfor primary content visibilityCLSfor layout stability- route transition latency for app navigation
- mutation confirmation time for interactive flows
The critical detail is that lab scores alone are not enough. A frontend can score well and still feel awkward if transitions are blocked, loading states are vague, or layout shifts happen in the exact moments users are trying to act.
Real user monitoring is the only way to see that.
9. My rule of thumb
A frontend feels fast when the user can answer three questions immediately:
- Did the interface register my action?
- Can I already do the next thing?
- Is this screen stable enough to trust?
Streaming, optimistic UI, transitions, image strategy, and scheduling are all tools for improving those three answers.
That is the modern shift. Performance is no longer only about delivering bytes faster. It is about structuring feedback, rendering, and stability so the browser always has something confident and useful to show next.