Web Performance

Optimize INP with useTransition: Boost React UX & Google Rankings

Interaction to Next Paint (INP) is now a critical Core Web Vital, directly impacting user experience and search visibility. Learn how to diagnose and dramatically improve your INP score in React applications using advanced techniques like useTransition and scheduler.yield() for non-blocking updates.

Krapton Engineering
Reviewed by a senior engineer11 min read
Share
Optimize INP with useTransition: Boost React UX & Google Rankings

In 2026, user expectations for web application responsiveness are higher than ever. Google's recent shift to Interaction to Next Paint (INP) as a Core Web Vital underscores this, making smooth, non-blocking user interactions paramount not just for user satisfaction, but for your site's search engine rankings. A sluggish UI isn't just annoying; it's a direct signal to Google that your page offers a poor experience.

TL;DR: Interaction to Next Paint (INP) measures a page's responsiveness to user input, and it's a critical ranking factor. By leveraging React's useTransition hook and advanced JavaScript scheduling with scheduler.yield(), developers can effectively defer non-urgent UI updates, prevent main thread blocking, and significantly improve INP scores for a snappier user experience and better SEO.

Key takeaways

From above of modern portable computer with open analytical program on screen on white table
Photo by Василь Вовк on Pexels
  • INP is a Critical Ranking Factor: Google's INP metric directly impacts search visibility, measuring the responsiveness of every user interaction.
  • Identify Long Tasks: Use Chrome DevTools Performance tab and CrUX data to pinpoint interactions causing main thread blocking and high INP scores.
  • Leverage useTransition: Wrap non-urgent state updates in startTransition to make them interruptible and prevent UI freezes, especially in React 18+.
  • Integrate scheduler.yield(): For fine-grained control or non-React tasks, scheduler.yield() offers a powerful way to break up long JavaScript tasks and return control to the main thread.
  • Verify with Field Data: Always confirm INP improvements using real-user monitoring (RUM) tools and PageSpeed Insights' field data, as lab data can be misleading.

Understanding Interaction to Next Paint (INP)

Close-up of business analytics charts and graphs on papers and clipboard.
Photo by RDNE Stock project on Pexels

Interaction to Next Paint (INP) measures the latency of all user interactions on a page, from the moment a user clicks, taps, or types, to the moment the browser paints the next frame showing the visual update. Instead of just measuring the first input, INP observes the entire lifecycle of a page and reports a single, representative value (the worst interaction, excluding outliers) at the 75th percentile for all page loads.

Why is this critical? A high INP score—typically above 200 milliseconds (ms)—means users are experiencing noticeable delays between their actions and your application's response. This directly translates to frustration, bounces, and ultimately, lost conversions. Since March 2024, INP has replaced First Input Delay (FID) as a Core Web Vital, making it a direct signal for Google's Page Experience ranking signal. Ignoring INP means risking your organic search visibility.

Diagnosing High INP Scores: Field vs. Lab Data

Before optimizing, you need to know where your INP stands. Google emphasizes field data (real user data from the Chrome User Experience Report, or CrUX) over lab data (simulated tests like Lighthouse). While Lighthouse provides actionable diagnostics in a controlled environment, CrUX data reflects what real users experience across various networks and devices.

  1. CrUX Report & PageSpeed Insights: Start here. PageSpeed Insights will show your site's INP field data (if available), indicating whether you're passing (under 200ms), needs improvement (200-500ms), or failing (over 500ms).
  2. Chrome DevTools Performance Tab: For granular debugging, open Chrome DevTools, go to the 'Performance' tab, and record a user interaction. Look for long tasks (red triangles, or tasks over 50ms) in the main thread flame chart. These often correspond to heavy JavaScript execution that blocks rendering and input processing. The 'Interactions' track will highlight specific INP issues.
  3. Real User Monitoring (RUM): Integrate a RUM solution (e.g., SpeedCurve, Sentry Performance, or custom implementation using the PerformanceObserver API and web-vitals.js library). This provides continuous, granular INP data from your actual user base, helping you track improvements over time and identify regressions.

In a recent client engagement, we observed a P75 INP of 650ms on a complex data dashboard built with React 17. The primary culprit was a search filter that re-rendered a large table with thousands of rows on every keystroke. This blocked the main thread for hundreds of milliseconds, making the input field feel unresponsive. Our initial attempt involved aggressive debouncing, which helped, but didn't solve the core problem of expensive rendering.

Optimize INP with useTransition for Non-Blocking Updates

React 18+ introduced concurrent rendering, a paradigm shift enabling the framework to prepare UI in the background without blocking the main thread. The useTransition hook is your primary tool for leveraging this. It allows you to mark certain state updates as "transitions" – non-urgent updates that can be interrupted by more urgent ones (like user input).

How useTransition Works

When you wrap a state update in startTransition, React understands that this update can be deferred. If a more urgent update (e.g., another keystroke) occurs while the transition is pending, React will pause the transition, process the urgent update, and then resume or restart the transition. This keeps the UI responsive even during heavy computations.

Example: Filtering a Large List

Consider a search input that filters a large dataset. Without useTransition, typing rapidly can cause the input field to lag as React struggles to re-render the filtered list synchronously.

import React, { useState } from 'react';

function HeavyFilterList({ data }) {
  const [query, setQuery] = useState('');
  const [filteredData, setFilteredData] = useState(data);

  const handleChange = (e) => {
    const newQuery = e.target.value;
    setQuery(newQuery);
    // This synchronous update can block the main thread
    setFilteredData(data.filter(item => item.name.includes(newQuery)));
  };

  return (
    <div>
      <input type="text" value={query} onChange={handleChange} placeholder="Search..." />
      <ul>
        {filteredData.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

With useTransition, we can defer the filtering logic:

import React, { useState, useTransition } from 'react';

function HeavyFilterListWithTransition({ data }) {
  const [query, setQuery] = useState('');
  const [filteredData, setFilteredData] = useState(data);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    const newQuery = e.target.value;
    setQuery(newQuery);

    // Defer this update, allowing input to remain responsive
    startTransition(() => {
      setFilteredData(data.filter(item => item.name.includes(newQuery)));
    });
  };

  return (
    <div>
      <input type="text" value={query} onChange={handleChange} placeholder="Search..." />
      {isPending && <div>Loading...</div>}
      <ul style={{ opacity: isPending ? 0.5 : 1 }}>
        {filteredData.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

The isPending flag provides UI feedback, indicating that a transition is underway. This pattern significantly reduces perceived latency, as the input field remains snappy, even if the list update takes a moment.

When NOT to use this approach

While powerful, useTransition isn't a silver bullet. It's designed for non-urgent UI updates. Do not use it for critical, immediate feedback actions like form submissions, animations that must be perfectly smooth, or anything that absolutely requires synchronous execution. Overusing useTransition for every state update can introduce unnecessary complexity and potentially mask underlying performance issues that should be addressed at a lower level, such as optimizing rendering logic or data fetching. It's a tool for managing perceived performance, not for fixing inherently slow code.

Enjoying this article?

Like this article? Help us grow.

Choose Krapton as a preferred source on Google to see more of our engineering insights in Search. You only need to click once.

Advanced Control: Integrating scheduler.yield()

For scenarios beyond React's declarative updates, or when you need even finer-grained control over task scheduling, the experimental scheduler.yield() function (part of React's internal scheduler package) can be invaluable. This function explicitly tells the browser's main thread, "I'm done for now; you can process other high-priority tasks."

How scheduler.yield() Works

scheduler.yield() is similar in concept to requestIdleCallback but offers more control and integration with React's own scheduling. It allows you to break up long-running synchronous JavaScript tasks into smaller chunks, yielding control back to the browser between chunks. This prevents the main thread from becoming unresponsive, improving INP.

Example: Chunking a Heavy Computation

Imagine a scenario where you're performing a complex, non-React data transformation that takes hundreds of milliseconds. This can block the UI entirely.

import { unstable_scheduleCallback as scheduleCallback, unstable_shouldYield as shouldYield, unstable_ImmediatePriority as ImmediatePriority } from 'scheduler';

async function performHeavyComputation(data) {
  let result = [];
  for (let i = 0; i < data.length; i++) {
    // Simulate heavy calculation
    await new Promise(resolve => setTimeout(resolve, 0)); // Non-blocking wait
    result.push(data[i] * 2); // Or some complex operation

    if (shouldYield()) {
      // Yield control back to the browser
      console.log('Yielding to browser to prevent blocking...');
      await new Promise(resolve => scheduleCallback(ImmediatePriority, resolve));
    }
  }
  return result;
}

// Usage (e.g., in a useEffect or an event handler)
// const computedData = await performHeavyComputation(largeArray);

In this example, shouldYield() checks if the scheduler wants to yield. If so, we use scheduleCallback to defer the continuation of our heavy loop to the next available time slice. This keeps the UI responsive during the computation.

On a production rollout we shipped, the failure mode was a large, synchronously generated PDF preview on an invoicing application. This task would consistently block the main thread for over 800ms, causing users to perceive a frozen UI. By breaking down the PDF generation into smaller, yielded chunks, we reduced the perceived blocking time to under 50ms, dramatically improving the INP for that interaction.

Comparing Deferral Strategies

Choosing the right strategy depends on the context and the nature of the task. Here's a brief comparison:

StrategyUse CaseProsCons
useTransitionReact state updates for non-urgent UI changes (e.g., search filters, tab changes).Integrated with React's concurrent renderer, automatic interruption handling, built-in isPending state.Only for React state updates, requires React 18+.
useDeferredValueDeferring a value derived from props or state (e.g., an expensive computed value).Similar benefits to useTransition but for values, not direct state updates.Only for derived values in React, requires React 18+.
scheduler.yield()Breaking up long-running, CPU-bound JavaScript tasks, especially outside React's rendering cycle.Fine-grained control, can be used for any JavaScript task, highly effective for preventing main thread blocking.Experimental API (unstable_ prefix), more complex to implement, not directly for React state.
setTimeout(0)Simple deferral of a task to the next event loop tick.Easy to implement, widely compatible.No priority control, can lead to janky UI if overused or if subsequent tasks are also long.
requestIdleCallbackScheduling low-priority work during browser idle periods.Good for truly non-essential background tasks, integrates with browser's idle detection.Not guaranteed to run, limited browser support (as of 2026, still not universal), less predictable than scheduler.

Verifying Your INP Improvements

Once you've implemented these optimizations, verifying their impact is crucial. Always prioritize real-user data:

  1. PageSpeed Insights (Field Data): Re-run PageSpeed Insights after your changes have been deployed for a few days to a week. Look for improvements in the CrUX data for your INP score. This is the ultimate source of truth for Google.
  2. RUM Dashboards: Monitor your RUM solution's INP metrics. Look for a reduction in the P75 (and P90) INP score across your user base. This helps confirm the impact across diverse devices and network conditions.
  3. Chrome DevTools Performance Tab: Perform the same problematic interactions you identified earlier. Verify that 'Long Tasks' are reduced or eliminated, and the 'Interactions' track shows significantly shorter delays.
  4. web-vitals.js in Development: During development, use the web-vitals.js library to log INP to the console or your analytics. This gives immediate feedback on local changes.

Our team measured a reduction in P75 INP from 650ms to 120ms on the client's dashboard by strategically applying useTransition to the search filter and optimizing the underlying data processing. This not only significantly improved user satisfaction but also led to a measurable uplift in the site's overall Page Experience score in Google Search Console.

Krapton's Approach to INP Optimization

At Krapton, we understand that optimizing Core Web Vitals, especially a nuanced metric like INP, requires deep technical expertise combined with a strategic understanding of business impact. Our principal-level software engineers conduct comprehensive performance audits, starting with a thorough analysis of your site's CrUX data and detailed lab diagnostics.

We identify critical interaction bottlenecks, often leveraging advanced React features, Web Workers, and efficient data processing techniques to eliminate long tasks. Whether it's refactoring complex UI components with useTransition, implementing Next.js performance best practices, or optimizing server-side rendering, our goal is to deliver measurable improvements that boost both user experience and organic search rankings. We ensure that your applications are not just fast, but feel fast and responsive to every user, every time.

FAQ

What is a good INP score?

According to Google, an INP score of 200 milliseconds or less is considered "Good." Scores between 200ms and 500ms "Need improvement," and anything over 500ms is considered "Poor," indicating significant responsiveness issues.

Does INP impact SEO?

Yes, INP is a Core Web Vital, which is a key component of Google's Page Experience signal. A poor INP score can negatively impact your search engine rankings and overall visibility, especially in competitive niches.

Can I improve INP without React 18?

While React 18's concurrent features like useTransition are powerful, you can still improve INP in older React versions or vanilla JavaScript. Strategies include debouncing/throttling inputs, using requestIdleCallback, offloading heavy computations to Web Workers, and optimizing rendering logic to reduce component re-renders.

What is the difference between INP and FID?

First Input Delay (FID) measured only the delay before the browser could begin processing the first user interaction. Interaction to Next Paint (INP) is a more comprehensive metric, measuring the full latency of all interactions from input delay to processing time and presentation delay, providing a more accurate reflection of overall page responsiveness.

How long does it take for INP changes to reflect in PageSpeed Insights?

CrUX data, which powers the field data in PageSpeed Insights, is collected over a 28-day rolling window. Therefore, it can take several days to a few weeks for significant INP improvements to be fully reflected in your PageSpeed Insights report and Google Search Console.

Ready to Supercharge Your Web Application's Performance?

Don't let slow interactions hinder your user experience or your search rankings. Krapton's engineering team specializes in diagnosing and fixing Core Web Vitals issues, leveraging cutting-edge techniques to ensure your applications are lightning-fast and highly responsive. Improve your INP and overall site health today. Run a free SEO audit with Krapton's SEO Analyzer to get an instant snapshot of your site's Core Web Vitals performance.

About the author

Krapton Engineering brings over a decade of hands-on experience shipping high-performance web and mobile applications for startups and enterprises globally, with a deep specialization in React, Next.js, and complex performance optimization for Core Web Vitals.

core web vitalsweb performanceINPreact performanceuseTransitionscheduler.yieldpage speedSEO performancejavascript optimization
About the author

Krapton Engineering

Krapton Engineering brings over a decade of hands-on experience shipping high-performance web and mobile applications for startups and enterprises globally, with a deep specialization in React, Next.js, and complex performance optimization for Core Web Vitals.