Master the View Transitions API and content-visibility to create instantaneous navigation. Learn how these tools improve perceived performance in real apps.
Last month, I spent about three days trying to convince a dashboard to feel "instant." The data was heavy, the DOM was bloated, and every time a user clicked a sidebar link, the browser stuttered for roughly 200ms before painting the next view. It wasn't a total failure, but it felt sluggish.
I decided to stop obsessing over raw load times and start hacking the user’s perception. By combining the View Transitions API with strategic content-visibility usage, I managed to shave that "perceived" lag down significantly.
When you navigate between pages, the browser tears down the current DOM and rebuilds the new one. Even with fast frameworks, the browser’s main thread has to perform style calculation, layout, and painting for the entire page.
If you're dealing with large datasets, you might be suffering from Optimizing Forced Synchronous Layout: A Guide to Better INP, which happens when you trigger constant reflows. We used to solve this by skeleton screens or spinners, but those are just bandaids for a deeper rendering bottleneck.
Before worrying about how the transition looks, we had to fix the rendering cost. The content-visibility CSS property is effectively a "lazy-loading" mechanism for your DOM. By setting content-visibility: auto on off-screen components, you tell the browser to skip rendering work for elements that aren't in the viewport.
Here is how I applied it to our main content area:
CSS#9CDCFE">color:#4EC9B0">.dashboard-widget { #9CDCFE">content-visibility: auto; #9CDCFE">contain-intrinsic-size: 0 500px; }
The contain-intrinsic-size is crucial. Without it, the scrollbar jumps around like crazy because the browser doesn't know the height of the hidden elements. Setting a rough estimate gives the browser a placeholder height. I've found that using this alongside Progressive Hydration: Boosting Perceived Performance on Large Dashboards can make a significant difference in how the browser handles heavy React or Vue trees.
Once the rendering was optimized, the jump between pages still felt "mechanical." This is where the View Transitions API shines. Instead of a hard cut, you can capture a snapshot of the current state and animate it into the next.
In Chrome 111+, you can trigger a transition with a single line of code:
JAVASCRIPTdocument.startViewTransition(() => { updateDOMState(); // Your function to swap the page content });
It’s surprisingly simple, but the "gotchas" are real. If your CSS animations are too heavy, you’ll actually tank your INP (Interaction to Next Paint) score. I initially tried to animate the entire page container, but the browser struggled with the snapshot generation for larger layouts.
I had to scope the transition specifically to the header and the main content area using the view-transition-name CSS property:
CSS#9CDCFE">color:#4EC9B0">.main-header { #9CDCFE">view-transition-name: main-header; } #9CDCFE">color:#4EC9B0">.content-grid { #9CDCFE">view-transition-name: content-grid; }
This scoped approach keeps the snapshot small, keeping the transition smooth.
Using content-visibility and the View Transitions API isn't a silver bullet for perceived performance. I hit a few walls:
content-visibility, screen readers might have trouble if you haven't managed the aria-hidden states correctly.Q: Does content-visibility break my search functionality? A: Yes, it can. Since the browser doesn't render hidden elements, standard "Find in Page" (Ctrl+F) might fail to find text inside those sections. Use it for off-screen UI components, but be careful with core textual content.
Q: Why is my transition flickering?
A: Usually, this is because you're triggering a layout shift during the snapshot phase. Ensure your view-transition-name elements don't change size or position abruptly during the transition.
Q: Is this better than client-side routing? A: It’s not an "either/or." You should use client-side routing for the logic and these browser APIs to handle the visual rendering performance.
I'm still tinkering with the timing of these transitions. I’ve noticed that if the animation lasts longer than 300ms, users start feeling like the app is "laggy" rather than "smooth." Finding that sweet spot between 150ms and 250ms seems to be the key to hitting that "instant" feel without annoying the user. Next time, I think I’ll look into how we can pre-fetch the DOM structure itself to make the transition even snappier.
Master third-party script optimization using Partytown and Web Workers. Learn how to stop main thread blocking and keep your site fast and responsive.
Read moreCumulative Layout Shift (CLS) ruins user experience. Learn how to stop UI jitter using CSS containment and aspect-ratio properties for a stable interface.