INP has become a critical Core Web Vitals metric — since March 2024 it replaces FID and accounts for all user interactions. Poor INP (over 200 ms) directly reduces conversion: each 100 ms delay in input or click drives away 1–2% of visitors. We help clients achieve ≤200 ms and confirm the result with instrumental monitoring through a combination of Long Tasks splitting, async filtering, and offloading heavy computations to Web Workers.
How INP Works
INP = time from user action (mousedown, keydown, pointerdown) to the next browser frame paint. The delay consists of three phases: input delay — waiting for the main thread to become free; processing time — executing event handlers; presentation delay — layout, paint, composite. A typical scenario: the user clicks a filter button, but the main thread is busy parsing an analytics script — the click waits 120 ms, then the handler runs for 80 ms, plus 50 ms for rendering — total 250 ms, already beyond acceptable.
Why INP Is Critical for SEO?
Google made INP one of three key ranking signals. Sites with INP > 200 ms lose rankings and get less organic traffic. For e-commerce, every millisecond of delay reduces conversion by 1–2%. In one project after reducing INP from 350 ms to 80 ms, conversion increased by 12%, and average depth per visit grew by 2 pages.
Diagnosing Slow Interactions
Use PerformanceObserver to collect all interactions with a delay >16 ms:
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 200) {
console.warn(`Slow interaction: ${entry.name}`, {
duration: entry.duration,
processingStart: entry.processingStart,
processingEnd: entry.processingEnd,
inputDelay: entry.processingStart - entry.startTime,
processingTime: entry.processingEnd - entry.processingStart,
presentationDelay: entry.startTime + entry.duration - entry.processingEnd,
});
}
}
}).observe({ type: 'event', buffered: true, durationThreshold: 16 });
In Chrome DevTools → Performance → record the page → filter Long Tasks. Any task >50 ms is a candidate for optimization. In real sessions we often see Long Tasks from 100 to 500 ms, caused by third-party scripts or heavy main-thread computations.
Eliminating Long Tasks: Two Approaches
Splitting via yield suits lightweight computations. Web Worker is better for CPU-intensive tasks as it completely frees the main thread. A real case: in an online store, filtering 20,000 products in real time gave INP of 350 ms. We moved filtering to a Web Worker and added list virtualization — INP dropped to 80 ms.
// Async filter with yield every 50 elements
async function filterProductsAsync(products, filters) {
const results = [];
for (let i = 0; i < products.length; i++) {
if (matchesFilters(products[i], filters)) {
results.push(products[i]);
}
if (i % 50 === 0 && i > 0) {
await scheduler.yield(); // Chrome 115+
// fallback: await new Promise(r => setTimeout(r, 0));
}
}
return results;
}
// Web Worker – offload heavy filtering
// worker.js
self.onmessage = function({ data: { products, filters } }) {
const results = products.filter(p => matchesFilters(p, filters));
self.postMessage(results);
};
// main.js
const worker = new Worker('/js/filter-worker.js');
worker.postMessage({ products, filters });
worker.onmessage = ({ data }) => setFilteredProducts(data);
Optimizing React Components
Problem: synchronous filtering on every keystroke blocks the main thread. Solution — useTransition and virtualization. Here's a search component with instant feedback:
function ProductList() {
const [query, setQuery] = useState('');
const [deferredQuery, setDeferredQuery] = useState('');
const filtered = useMemo(
() => products.filter(p => p.name.toLowerCase().includes(deferredQuery.toLowerCase())),
[deferredQuery]
);
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const value = e.target.value;
setQuery(value); // urgent – input responds immediately
startTransition(() => {
setDeferredQuery(value); // non-critical – list updates later
});
}
return (
<>
<input value={query} onChange={handleChange} />
<ul>
{filtered.map(p => <ProductItem key={p.id} product={p} />)}
</ul>
</>
);
}
For long lists, use virtualization:
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualProductList({ products }: { products: Product[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const rowVirtualizer = useVirtualizer({
count: products.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 80,
overscan: 5,
});
return (
<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
<div style={{ height: rowVirtualizer.getTotalSize() }}>
{rowVirtualizer.getVirtualItems().map(virtualRow => (
<div key={virtualRow.index}
style={{ transform: `translateY(${virtualRow.start}px)`, position: 'absolute', width: '100%' }}>
<ProductItem product={products[virtualRow.index]} />
</div>
))}
</div>
</div>
);
}
Third-party Scripts: Hidden Threat
Chats, pixels, analytics — common causes of poor INP. They run on the main thread and block interactions. Solutions:
- Load after main content (setTimeout 3s after load).
- Use Partytown to run scripts in a Web Worker.
Typical INP Optimization Mistakes
- Optimizing only one interaction, while INP takes the worst.
- Ignoring presentation delay — sometimes paint takes longer than the handler.
- Using
setTimeout(0)instead ofscheduler.yield()— the former doesn't guarantee yielding the thread. - Forgetting mobiles: on weak CPUs Long Tasks occur more often.
How to Measure INP in Real Time?
Set up Real User Monitoring (RUM) sending metrics to analytics. Filter bots using navigator.webdriver. Example: collect all interactions with INP >200 ms into performanceEntries and send to backend for analysis. This reveals problematic pages and devices.
INP Target Values
| Interaction type | Target |
|---|---|
| Button click | < 100 ms |
| Input in search field | < 150 ms |
| Opening modal | < 200 ms |
| Catalog filtering | < 200 ms |
INP Optimization Process
- Diagnostics — collect real metrics via RUM and lab tests.
- Analysis — identify top 5 problematic interactions with exact delay values.
- Implementation — apply techniques (yield, Workers, lazy loading, virtualization).
- Testing — A/B experiments, checking impact on conversion and SEO traffic.
- Monitoring — set up continuous INP tracking with alerts when exceeding 200 ms.
Estimated Timeline
From 3 to 7 days — depending on number of problematic interactions and architectural complexity. Pricing is determined individually after an audit.
Over 50 successful INP optimizations. We guarantee achieving ≤200 ms or your money back. Contact us for an audit and receive an optimization plan within 3 days. Request a consultation — we'll review your case for free.







