Improving INP is essential for a responsive UI. Learn how to use React Suspense and selective hydration to minimize main thread blocking and boost performance.
Last month, our dashboard’s INP score tanked after we introduced a heavy data-fetching hook into our primary navigation. We were blocking the main thread for over 400ms during the hydration phase, making the UI feel frozen whenever a user tried to interact with the sidebar immediately after load.
If you’re seeing high INP scores, you’re likely dealing with a "long task" problem where the browser is too busy executing JavaScript to process user input. Improving INP Optimization: Strategies to Reduce Input Delay and Long Tasks requires a shift from monolithic hydration to a more surgical approach.
Interaction to Next Paint (INP) measures the latency between a user interaction and the next frame update. When your app is busy hydrating large chunks of state or running intensive data transformations, it can't respond to clicks or keystrokes.
We initially tried to fix this by moving our useEffect logic, but React derived state: Stop using useEffect for data calculations taught us that the problem wasn't just the hook—it was the volume of work happening at once. We were flooding the main thread during the initial render.
To solve this, we moved away from eager loading. By wrapping our heavy components in <Suspense>, we allowed React to prioritize the critical UI paths first.
Selective hydration allows React to hydrate parts of the page independently. If a user interacts with a sidebar while the main chart is still fetching data, React can pause the chart's hydration and prioritize the sidebar's event listeners.
Here is how we structured our main layout:
JSX// Layout.js import { Suspense } from CE9178">'react'; export default function DashboardLayout({ children }) { return ( <main> <Sidebar /> <Suspense fallback={<ChartSkeleton />}> <HeavyAnalyticsChart /> </Suspense> </main> ); }
By isolating HeavyAnalyticsChart, we prevented it from blocking the interaction handlers of the Sidebar. This change alone brought our INP down from an abysmal 550ms to a much more respectable 180ms.
While Suspense helps, it’s not a silver bullet if your data-fetching patterns are flawed. We had to rethink how we requested data across the component tree. If you're fetching data in deeply nested components, you might be creating waterfalls that keep the main thread busy for longer than necessary.
I recommend checking out our guide on Next.js App Router Data Fetching: Avoiding Performance Waterfalls to ensure your requests are parallelized. When you combine parallel data fetching with selective hydration, you significantly reduce the time the browser spends in long-running tasks.
Web performance optimization is often a game of trade-offs. While we wanted the fastest possible load, we had to accept that some content would "pop in" later. We found that the trade-off—a slight delay in the chart rendering vs. a fully responsive navigation—was a massive win for user experience.
Remember, Core Web Vitals aren't just about loading metrics like LCP; they are holistic. A fast-loading page that ignores your clicks is a broken page. By focusing on INP and keeping the main thread clear, you ensure your app actually feels fast to the person using it.
If I were to refactor this again, I’d be more aggressive with code-splitting from day one. We waited until we had a performance issue to split our heavy components, which made the refactor more complex than it needed to be.
We’re still experimenting with how much granularity we should apply to our Suspense boundaries. Too many boundaries can lead to a "flickering" UI, which is its own kind of bad UX. It’s a delicate balance, and we’re still learning exactly where the sweet spot lies for our specific traffic patterns.
Does Suspense fix INP by itself? No. Suspense helps by enabling selective hydration, which breaks up long tasks. You still need to ensure your underlying data fetching doesn't block the main thread.
How do I measure if my hydration is too heavy? Use the Chrome DevTools "Performance" tab. Look for "Long Tasks" (marked with red triangles) during the loading phase. If you see them, your hydration is likely too heavy.
Is it possible to over-optimize with Suspense? Yes. Creating too many small boundaries can make the UI feel disjointed. Use them specifically for heavy, non-critical components that don't need to be available for the initial interaction.
Master third-party script optimization using Partytown and Web Workers. Learn how to stop main thread blocking and keep your site fast and responsive.