Performance & engineering / Field notes

Engineering Sub-200ms INP for a Next.js Storefront: A Worked Performance Case Study

A practical Next.js e-commerce INP case study: diagnose slow interactions, reduce JavaScript, manage long tasks and verify real-user performance.

Web Dev Awards brand artwork
WEB DEV AWARDS / ENGINEERING JOURNAL

Worked technical case study (illustrative, not verified submission data)

THE SHORT ANSWER

What to know

To investigate slow storefront interactions, separate input delay, event processing and presentation delay. Reduce unnecessary client work, keep immediate feedback responsive and test failures deliberately. This worked example is hypothetical: a single 420ms trace is not a verified site-wide INP result, and any claimed improvement requires real-user evidence.

A product page can load quickly and still feel slow. The image appears, the price is visible and the navigation works, but selecting a colour or adding an item to a basket takes long enough for the visitor to wonder whether the click registered. This is the kind of performance problem a loading-speed score can miss.

This technical case study walks through a hypothetical, but reproducible, e-commerce investigation. It is not an account of a named Web Dev Awards submission, and its diagnostic timings are illustrative rather than measured results from a live retailer. The objective is to show how a team could identify, prioritise and verify the engineering work required to bring Interaction to Next Paint (INP) below 200 milliseconds.

The problem: fast loading, slow interaction

Imagine a Next.js storefront serving a catalogue of configurable products. Each product can have several colours and sizes, different inventory availability, a gallery, delivery estimates and an add-to-basket action. The frontend also loads analytics, remarketing and customer-support integrations. Its initial product page looks polished, but interactions occasionally stall on mid-range mobile devices.

A slow product-variant interaction is a useful diagnostic example. A single interaction trace might show 420ms between input and the next visual update. That is not the same thing as saying the site's 75th-percentile INP is 420ms. INP is assessed across qualifying interactions over the life of each page visit; the public Core Web Vitals threshold is evaluated at the 75th percentile of visits, separately for mobile and desktop. An INP of 200ms or less is considered good, while more than 500ms is poor. Google's INP documentation explains the metric and its thresholds.

The user's experience is straightforward: tap a variant, see no response, tap again, and possibly make an unintended second selection. A fast server response does little to compensate when the main thread cannot respond or paint.

Deconstructing the interaction

INP combines three parts of responsiveness: input delay, event processing time and presentation delay. A hypothetical 420ms diagnostic trace could look like this:

Part of the interactionIllustrative durationWhat the trace suggests
Input delay130msA previously scheduled script prevents the event handler from starting promptly.
Event processing190msVariant selection triggers synchronous filtering, pricing calculations and costly component work.
Presentation delay100msLayout and rendering work delay the next visual frame.
Total420msOne slow interaction, not a verified site-wide INP result.

These are diagnostic categories, not three independent scores to optimise blindly. A slow interaction may also involve network requests, but the INP timing concerns the period from user input to the next paint. An asynchronous network operation should not prevent an immediate acknowledgement that the action was received.

Before changing code, reproduce the problem. Record a Chrome DevTools Performance trace on representative mobile hardware or with realistic CPU throttling. Interact with swatches, quantity selectors, filters and basket controls. Look for long tasks immediately before the event, expensive work inside the handler and layout or paint activity after it. Repeat the action after the page has been open for a while: some third-party scripts load later, and INP is not limited to the first click.

Establish a trustworthy baseline

A defensible performance report records the page or page group, mobile and desktop cohorts, observation window, measurement source, release version and the interaction being investigated. PageSpeed Insights and the Chrome User Experience Report (CrUX) can provide real-user context when sufficient data exists, but they may not isolate a particular product swatch or cart action. Real User Monitoring (RUM) with interaction attribution is more useful for locating the actual cause. Google's INP measurement walkthrough shows how to use the web-vitals attribution build.

For local diagnosis, the following JavaScript records updated INP candidates in the console:

import { onINP } from 'web-vitals/attribution';

// Debugging only. For production RUM, use an approved endpoint and
// avoid collecting text, customer details or sensitive selectors.
onINP((metric) => {
  console.log({
    value: metric.value,
    target: metric.attribution?.interactionTarget,
    inputDelay: metric.attribution?.inputDelay,
    processingDuration: metric.attribution?.processingDuration,
    presentationDelay: metric.attribution?.presentationDelay,
  });
}, { reportAllChanges: true });

This example is deliberately a diagnostic aid, not a complete telemetry service. Monitoring should respect consent requirements, minimise personal data and avoid logging form values or identifiable customer information.

Intervention one: shrink the client-side work

A common mistake is treating every part of a React application as equally interactive. In Next.js App Router, Server Components can handle data fetching and non-interactive content without shipping their component logic to the browser. A swatch selector and basket controls still require Client Components, but the product description, static editorial content and much of the layout often do not.

The aim is not to eliminate hydration. It is to keep the client boundary narrow. A page-level use client directive can pull much more code into the client dependency graph than necessary. Move the boundary down to components that genuinely use event handlers, browser APIs or client state, and inspect the resulting bundle. Next.js documents this distinction.

For the variant selector, avoid making one click synchronously rebuild an entire product page. Hold the selected variant in a small state owner; derive only the immediate price, availability and gallery changes; memoise expensive calculations only where profiling demonstrates a benefit. Avoid copying server state into several competing client stores. If the backend has the final say on stock or price, reconcile the visible selection with the server response rather than treating optimistic values as guaranteed.

What to test: Does the variant label or selected-state indicator update quickly? Does the browser still stall when a large gallery is present? Does a failed inventory request leave the page usable? Are screen-reader users told when availability changes?

Intervention two: separate urgent feedback from expensive work

The user needs to know that a selection has registered. They do not need every recommendation, filtered result and analytics calculation to finish before any visible acknowledgement appears.

Start by removing unnecessary synchronous work from the event handler. For computationally expensive local operations, a Web Worker may be more appropriate than the main thread. If processing must occur on the main thread, divide it into bounded tasks so that input and rendering can run between chunks.

async function yieldToMain() {
  if (globalThis.scheduler?.yield) {
    return globalThis.scheduler.yield();
  }
  return new Promise((resolve) => setTimeout(resolve, 0));
}

async function processInBatches(items, processItem) {
  let lastYield = performance.now();

  for (const item of items) {
    processItem(item);
    if (performance.now() - lastYield >= 40) {
      await yieldToMain();
      lastYield = performance.now();
    }
  }
}

This is an example of cooperative scheduling, not a universal 40ms rule. scheduler.yield() is not supported everywhere, hence the fallback. Yielding can make a long process less disruptive, but it does not guarantee that a particular React state update has painted. Use DevTools and real-device testing to verify visible feedback. React's startTransition and useDeferredValue can also help distinguish urgent state updates from non-urgent rendering; neither makes heavy synchronous computation magically disappear. See Google's guidance on optimising long tasks.

An especially useful test is to tap two different variants in quick succession. The final screen must represent the latest selection. If requests complete out of order, cancel or disregard stale responses. Performance improvements that introduce inconsistent prices, quantities or availability are regressions, not wins.

Intervention three: audit third-party JavaScript

Tag managers, pixels, personalisation engines and support widgets often accumulate without a clear owner. Their cost is not confined to network transfer. Some execute repeatedly, register listeners or cause layout work precisely when a customer tries to interact.

Build a script inventory: what each integration does, where it runs, whether it is needed before interaction, who owns it and what happens if it fails. Remove redundant tags. Load non-essential services later where doing so does not break required measurement or functionality. A delayed script must not silently erase consent choices, purchase attribution or customer-support access.

Tools such as Partytown may move selected third-party scripts to a worker, but compatibility is not automatic. Scripts that depend heavily on synchronous DOM access or main-thread event behaviour can malfunction, and worker-to-main-thread communication still has a cost. Treat worker execution as a tested option, not a blanket solution. Equally, the checkout must remain functional if an advertising pixel or chat widget fails.

Intervention four: make basket mutations resilient

A basket action is both an interaction and a transaction. A robust interface gives immediate, accessible feedback without pretending that a server mutation has succeeded before it has.

When an item is added, the button can enter a pending state and expose an appropriate status message. The request should be protected against duplicate submission, timeouts and stale responses. On success, update the basket count and announce the result. On failure, preserve the customer's selection and offer an understandable retry. Where optimistic updates are used, define the rollback behaviour and test it deliberately.

This is where performance, accessibility and reliability converge. A rapid visual update is useful only if the cart is accurate, keyboard focus remains predictable and a screen reader receives the right information.

Verification: what counts as an improvement?

Run before-and-after interaction traces on the same test hardware. Compare the location and duration of long tasks, JavaScript execution, rendering and user-visible feedback. Then evaluate real-user data over comparable windows, controlling as far as possible for device mix, geography, traffic source, release changes and seasonality.

EvidenceWhat it establishesWhat it does not establish
Chrome Performance traceWhy a particular interaction is delayedThe whole audience's 75th-percentile INP
Lighthouse Total Blocking TimeA lab signal of main-thread pressure during loadA direct replacement for INP
CrUX / PageSpeed Insights field dataReal-user Core Web Vitals where sufficient samples existThe exact component causing a slow interaction
First-party RUM with attributionWhich interactions and cohorts have problemsAutomatic proof that one code change caused all improvements
Conversion analyticsWhether business outcomes changedCausation without a suitable experimental design

A credible case study would publish the actual baseline and follow-up INP distributions, sample counts, collection dates, device cohorts, deployment changes and limitations. It would not turn an isolated trace into a site-wide result. Nor would it attribute a conversion uplift to performance work without a well-designed comparison or controlled experiment. An example target of sub-200ms field INP is a testable goal; a claimed outcome needs evidence.

The review perspective

At Web Dev Awards, performance sits alongside technical execution, accessible interaction and creative development. A technically ambitious storefront is not weakened by using React, animation or third-party services. The question is whether the implementation supports people using the site under realistic conditions.

The strongest performance work is usually invisible. Selecting a size feels immediate, the gallery remains stable, the basket reports its true state and the page continues working when an optional integration fails. That is a better outcome than a perfect-looking screenshot paired with a fragile, delayed checkout.

Frequently asked questions

Is an INP of 200ms a hard requirement for every single click?

No. Google's good threshold is assessed at the 75th percentile of page visits, segmented by mobile and desktop. Individual slow interactions are still important diagnostic evidence and can be particularly damaging on revenue-critical journeys.

Will converting everything to Server Components fix INP?

No. Server Components can reduce client JavaScript, but interactive controls still need browser-side code. Expensive event handlers, rendering, layout and third-party work can still produce poor responsiveness.

Does a lower Total Blocking Time prove INP improved?

No. TBT can help explain main-thread load in the lab, whereas INP measures user interaction responsiveness. Compare both where useful, and verify INP using real-user measurements.

Can a performance improvement be credited with higher conversion?

It may contribute, but a correlation alone does not establish causality. Report the observation accurately and use controlled experiments where possible.

Further reading: Interaction to Next Paint · Optimise INP · Next.js Server and Client Components · Web Dev Awards judging