We use cookies to improve your experience, analyze site traffic, and personalize content. You can accept all cookies or choose which categories to allow. Learn more
INP Optimization for Product Detail Pages (PDP) in 2026 | Ordiko
Guide
INP Optimization for Product Detail Pages (PDP) in 2026
A focused guide to fixing Interaction to Next Paint (INP) on ecommerce PDPs in 2026, covering variant swap, gallery, add-to-cart, and the React Server Components patterns that consistently move INP under 200ms.
PT1H30M
TL;DR. INP on PDP is fixed by shipping less JavaScript and making interactions optimistic. Use React Server Components for static parts. Use useOptimistic for variant swap and add-to-cart. Use startTransition for non-urgent updates. Debounce search with AbortController. Hoist regex and avoid spread in event handlers. Target: < 200ms p75, achievable < 100ms.
What INP measures
Interaction to Next Paint (INP) measures the longest interaction-to-paint time on a page during the user's visit. An "interaction" is a click, tap, or keypress that triggers JavaScript.
INP is calculated as the worst case (excluding rare outliers), not the average. One slow filter click can ruin your INP score even if 99 other interactions were fast.
INP value
Verdict
β€ 200ms
Good
200ms β 500ms
Needs improvement
> 500ms
Poor
Step 1: Profile, don't guess
FAQ
Why is INP so much harder than LCP?
LCP is mostly a CDN and image-optimization problem with well-understood fixes. INP is JavaScript-execution-time on user interaction; each click can have a different bottleneck. Fixing INP requires component-level discipline across every interactive element on the page.
Does React Server Components fix INP automatically?
Largely yes. RSC reduces the JS bundle shipped to the browser, which reduces main-thread work, which improves INP. But interactive parts of the page still need client JS β RSC alone won't fix a poorly-written client component that creates regex in onClick.
What's the single biggest INP killer on PDPs?
Heavy client-side rendering of recommendation widgets ('Frequently bought together', 'You might also like') that ship 50β200KB of JS and hydrate aggressively on first interaction. Move these to Server Components or lazy-load behind IntersectionObserver.
How fast can a well-optimized PDP get on INP?
Real-world p75 INP under 100ms is achievable. Ordiko PDPs default to 80β160ms p75 INP without manual optimization. Custom themes that follow the patterns in this guide can get below 100ms consistently.
Related reading
Use Chrome DevTools Performance tab to record a PDP interaction session. Look for:
Long tasks (yellow bars > 50ms) during interactions.
Forced reflows (purple bars).
Heavy attribution for INP entries.
Or use the Long Animation Frames (LoAF) API in your RUM:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 200) {
console.log('Long frame:', entry);
}
}
});
observer.observe({ type: 'long-animation-frame', buffered: true });
Step 2: Defer non-critical JS
The biggest INP wins on most PDPs come from removing or deferring scripts you didn't need to load eagerly:
// PDPPage (Server Component)
import { ProductGallery } from './product-gallery'; // Server
import { ProductInfo } from './product-info'; // Server
import { AddToCartButton } from './add-to-cart-button'; // Client (interactive)
import { VariantSelector } from './variant-selector'; // Client (interactive)
import { RelatedProducts } from './related-products'; // Server
import { ReviewSummary } from './review-summary'; // Server
export default async function PDP({ params }) {
const product = await getProduct(params.slug);
return (
<article>
<ProductGallery images={product.images} />
<ProductInfo product={product} />
<VariantSelector variants={product.variants} />
<AddToCartButton productId={product.id} />
<ReviewSummary reviews={product.reviews} />
<RelatedProducts productId={product.id} />
</article>
);
}
Only AddToCartButton and VariantSelector are Client Components. Everything else stays on the server. Bundle size drops 60β80% versus a fully-client PDP.
function handleClick(text) {
if (/[<>]/.test(text)) { ... } // regex created on every click
setItems(prev => [...prev, ...newItems]); // spread in accumulator
}
Good:
const TAG_REGEX = /[<>]/; // hoisted to module scope
function handleClick(text) {
if (TAG_REGEX.test(text)) { ... }
setItems(prev => prev.concat(newItems));
}
Step 8: Lazy-load below-the-fold
Recommendations, reviews, related products: put them below the fold and lazy-load with dynamic + ssr: false for purely client widgets, or load on intersection:
Why is INP so much harder than LCP? LCP is mostly a CDN and image-optimization problem with well-understood fixes. INP is JavaScript-execution-time on user interaction; each click can have a different bottleneck. Fixing INP requires component-level discipline across every interactive element on the page.
Does React Server Components fix INP automatically? Largely yes. RSC reduces the JS bundle shipped to the browser, which reduces main-thread work, which improves INP. But interactive parts of the page still need client JS β RSC alone won't fix a poorly-written client component that creates regex in onClick.
What's the single biggest INP killer on PDPs? Heavy client-side rendering of recommendation widgets ('Frequently bought together', 'You might also like') that ship 50β200KB of JS and hydrate aggressively on first interaction. Move these to Server Components or lazy-load behind IntersectionObserver.
How fast can a well-optimized PDP get on INP? Real-world p75 INP under 100ms is achievable. Ordiko PDPs default to 80β160ms p75 INP without manual optimization. Custom themes that follow the patterns in this guide can get below 100ms consistently.