FID / Interaction to Next Paint (INP) Optimization

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Optimizing FID and INP (interface responsiveness)

FID (First Input Delay) replaced by INP (Interaction to Next Paint) in March 2024. INP is stricter: measures all interactions during session, not just first. INP goal: ≤ 200 ms.

How INP works

INP = time from user action (mousedown, keydown, pointerdown) to next frame render by browser.

Delay consists of:

  1. Input delay — waiting for main thread to free from current task
  2. Processing time — execution time of event handlers
  3. Presentation delay — time until actual render (layout, paint, composite)

Diagnosing slow interactions

// Monitor all interactions
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 });

Chrome DevTools → Performance → record page → filter Long Tasks (red bar). Any task > 50 ms is optimization candidate.

Eliminating Long Tasks

Breaking synchronous computations:

// Before: blocks main thread for hundreds of ms
function filterProducts(products, filters) {
    return products.filter(p => matchesFilters(p, filters));
}

// After: yield every 50 items
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 for CPU-intensive tasks:

// 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);

React component optimization

Problem: excessive re-render on every keystroke:

// Bad: synchronous filtering on every keystroke
function ProductList() {
    const [query, setQuery] = useState('');
    const filtered = products.filter(p =>
        p.name.toLowerCase().includes(query.toLowerCase())
    );

    return <>
        <input onChange={e => setQuery(e.target.value)} />
        <ul>{filtered.map(p => <ProductItem key={p.id} product={p} />)}</ul>
    </>;
}

// Good: input field urgent, list deferred
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 — field responds instantly
        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>
    </>;
}

**Virtual

izing long lists:**

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>
    );
}

Event handler optimization

// Throttle for scroll/resize handlers
const handleScroll = throttle(() => {
    updateStickyHeader();
}, 16); // ~60fps

window.addEventListener('scroll', handleScroll, { passive: true });

// passive: true — tells browser handler won't call preventDefault
// Allows browser to scroll without waiting for JS

Third-party scripts

Chats, pixels, analytics — common cause of poor INP. They run in main thread and block interactions.

<!-- Load after main content -->
<script>
window.addEventListener('load', () => {
    setTimeout(() => {
        // Initialize chat/pixel
        loadChatWidget();
    }, 3000); // 3 second delay after load
});
</script>

Alternative — Partytown (Astro/Next.js): runs third-party scripts in Web Worker, completely freeing main thread.

INP targets

Interaction type Goal
Button click < 100 ms
Search input < 150 ms
Modal open < 200 ms
Catalog filter < 200 ms

Optimization time: 3–7 days depending on number of problematic interactions.