All articles
PerformanceSeptember 2, 2026 13 min read

INP Monitoring in Production: Complete RUM Setup Guide

Configure INP monitoring production RUM setup with web-vitals.js. Stream real user interaction latency to GA4 and BigQuery to triage high-p95 interactions.

To establish an INP monitoring production RUM setup, engineering teams must embed the official web-vitals library to capture the Event Timing API stream, extract the high-resolution breakdown of Input Delay, Processing Duration, and Presentation Delay, and beacon telemetry into BigQuery or Google Analytics 4 for 75th-percentile triage.

Interaction to Next Paint (INP) is Google's Core Web Vitals metric evaluating full-page interactive responsiveness. Unlike legacy lab metrics (such as Total Blocking Time) or the retired First Input Delay (FID), INP evaluates the latency of every tap, click, and keypress across the entire duration of a user's session. The final reported INP score corresponds to the worst interaction observed on the page (or the 98th percentile for sessions with many interactions).

Because synthetic laboratory tools cannot predict where, when, or how human visitors will interact with your application, relying solely on local Lighthouse audits is an operational blind spot. A site that scores 100 on local developer testing can easily fail INP in the field when real users click on un-hydrated React trees, un-memoized catalog filters, or third-party marketing widgets on budget mobile devices.

To pass Core Web Vitals in Google Search Console, you must monitor Real User Measurement (RUM) in production. In this engineering blueprint, we provide production-grade TypeScript code to capture INP with full sub-part attribution, configure scalable ingestion pipelines for GA4 and BigQuery, analyze why lab audits diverge from field reality, and demonstrate how automated synthetic testing complements live RUM monitoring.

For tactical guides on identifying and resolving specific interaction bottlenecks, review our deep dives on the 9 worst INP offenders in modern web apps, our engineering tutorial on fixing INP under 200ms, and our foundational guide to understanding what INP is and how to fix it.


The 3 Sub-Parts of an Interaction: Where Latency Actually Occurs

To diagnose an INP failure in production, engineers must understand that an interaction does not happen in a single step. The W3C Event Timing API breaks down every user interaction into three distinct, measurable phases:

SYSTEM ARCHITECTURE & FLOW
+-----------------------------------------------------------------------------------+
|                        THE 3 SUB-PARTS OF AN INP INTERACTION                      |
|                                                                                   |
|  [1. Input Delay]          [2. Processing Duration]      [3. Presentation Delay]  |
|  ├──────── 50ms ───────────┼────────── 90ms ─────────────┼────────── 40ms ────────┤
|  User Taps Screen       Event Handlers Start           Handlers Finish     Next Frame Painted
|                                                                                   |
|  1. Input Delay: Time waiting for main thread to clear before handlers run.       |
|  2. Processing Duration: Execution time of JavaScript event handler callbacks.    |
|  3. Presentation Delay: Time required for browser to recalculate style & paint.   |
+-----------------------------------------------------------------------------------+

1. Input Delay

Input delay measures the elapsed time from when the physical hardware detects a user touch, click, or keystroke until the browser's JavaScript event loop actually begins executing the corresponding event handler callback. If the main thread is currently saturated by a heavy background script, long hydration loop, or third-party analytics tag, the interaction is queued in the browser event buffer, causing high input delay.

2. Processing Duration

Processing duration is the total synchronous CPU time consumed by all JavaScript callbacks registered for that interaction event (e.g., pointerdown, pointerup, click). If an event listener runs a synchronous JSON parse on a 5MB payload or forces multiple React re-renders, the processing duration will skyrocket.

3. Presentation Delay

Presentation delay measures the time from when your JavaScript event callbacks finish executing until the browser's rendering engine actually draws the resulting visual changes to the display screen. This includes style recalculation, layout reflow, layer compositing, and GPU texture uploads.

To achieve Google's target threshold of ≤ 200ms at the 75th percentile, the mathematical sum of all three sub-parts must remain under 200 milliseconds:

$\text{Total INP} = \text{Input Delay} + \text{Processing Duration} + \text{Presentation Delay} \le 200\text{ms}$


Production Implementation: Capturing INP with web-vitals

Google maintains the official, open-source web-vitals JavaScript library to provide standard-compliant metric collection across modern browsers. Below is a production-ready TypeScript module that captures INP, extracts the high-resolution sub-part breakdown, and records the target DOM selector responsible for the interaction.

1. The RUM Telemetry Collector Module

TYPESCRIPT
// rum-telemetry-collector.ts
import { onINP, type INPMetric, type INPMetricWithAttribution } from 'web-vitals/attribution';

interface INPReportPayload {
  metric: 'INP';
  value: number; // Duration in milliseconds
  rating: 'good' | 'needs-improvement' | 'poor';
  interactionId?: number;
  interactionType?: string;
  targetSelector?: string;
  subparts: {
    inputDelay: number;
    processingDuration: number;
    presentationDelay: number;
  };
  navigationType: string;
  pageUrl: string;
  timestamp: string;
}

/**
 * Normalizes a DOM Node into a clean, human-readable CSS selector for debugging
 */
function getElementSelector(node: Node | null): string {
  if (!node || node.nodeType !== Node.ELEMENT_NODE) return 'unknown';
  const el = node as HTMLElement;
  if (el.id) return `#${el.id}`;
  if (el.dataset?.testid) return `[data-testid="${el.dataset.testid}"]`;

  const className = el.className && typeof el.className === 'string' 
    ? `.${el.className.trim().split(/\s+/).slice(0, 2).join('.')}` 
    : '';
  return `${el.tagName.toLowerCase()}${className}`;
}

/**
 * Initializes production Real User Measurement for Interaction to Next Paint
 */
export function initializeProductionINPMonitoring(endpointUrl: string) {
  onINP((metric: INPMetricWithAttribution) => {
    const attribution = metric.attribution;

    const payload: INPReportPayload = {
      metric: 'INP',
      value: Math.round(metric.value),
      rating: metric.rating,
      interactionId: attribution?.interactionId,
      interactionType: attribution?.interactionType,
      targetSelector: getElementSelector(attribution?.interactionTargetElement || null),
      subparts: {
        inputDelay: Math.round(attribution?.inputDelay || 0),
        processingDuration: Math.round(attribution?.processingDuration || 0),
        presentationDelay: Math.round(attribution?.presentationDelay || 0),
      },
      navigationType: metric.navigationType,
      pageUrl: window.location.pathname,
      timestamp: new Date().toISOString(),
    };

    // Use Navigator.sendBeacon to ensure data is transmitted even if user navigates away
    const blob = new Blob([JSON.stringify(payload)], { type: 'application/json' });
    if (navigator.sendBeacon) {
      navigator.sendBeacon(endpointUrl, blob);
    } else {
      fetch(endpointUrl, {
        method: 'POST',
        body: blob,
        keepalive: true,
        headers: { 'Content-Type': 'application/json' }
      }).catch((err) => console.warn('Failed to dispatch INP RUM payload:', err));
    }
  }, { reportAllChanges: false }); // reportAllChanges: false sends only the final session INP
}

Streaming RUM Telemetry to Google Analytics 4 (GA4)

If your organization uses Google Analytics 4, you can stream INP attribution directly into GA4 as custom event parameters without building custom backend endpoints:

TYPESCRIPT
// ga4-inp-bridge.ts
import { onINP, type INPMetricWithAttribution } from 'web-vitals/attribution';

declare global {
  interface Window {
    gtag?: (...args: any[]) => void;
  }
}

export function registerGA4INPTracking() {
  if (typeof window === 'undefined') return;

  onINP((metric: INPMetricWithAttribution) => {
    if (!window.gtag) return;

    window.gtag('event', 'web_vitals', {
      event_category: 'Web Vitals',
      event_action: 'INP',
      event_label: metric.id, // Unique session navigation ID
      value: Math.round(metric.value), // Duration in ms
      metric_rating: metric.rating,
      metric_target: metric.attribution?.interactionTarget || 'unknown',
      metric_input_delay: Math.round(metric.attribution?.inputDelay || 0),
      metric_processing_time: Math.round(metric.attribution?.processingDuration || 0),
      metric_presentation_delay: Math.round(metric.attribution?.presentationDelay || 0),
      non_interaction: true, // Prevents skewing bounce rate calculations
    });
  });
}

Scalable BigQuery Ingestion Architecture

For enterprise web applications handling millions of daily sessions, streaming raw interaction events directly into Google BigQuery enables SQL analysis of your 75th, 90th, and 95th percentile latency curves across device models, browser versions, and geographical regions.

SYSTEM ARCHITECTURE & FLOW
+-----------------------------------------------------------------------------------+
|                        ENTERPRISE RUM INGESTION PIPELINE                          |
|                                                                                   |
|  [Client Browser: web-vitals] ──> sendBeacon() JSON Payload                       |
|                                          │                                        |
|                                          ▼                                        |
|  [Cloudflare Worker / Edge API] ──> Fast API Gateway (Validates schema & CORS)    |
|                                          │                                        |
|                                          ▼                                        |
|  [Google Cloud Pub/Sub Broker]  ──> High-throughput streaming buffer               |
|                                          │                                        |
|                                          ▼                                        |
|  [Google BigQuery Data Lake]    ──> Columnar storage for p75 SQL triage           |
|                                          │                                        |
|                                          ▼                                        |
|  [Looker Studio Dashboard]      ──> Real-time executive monitoring & alerts       |
+-----------------------------------------------------------------------------------+

The BigQuery SQL Triage Query

Once your RUM events stream into BigQuery, execute this SQL query to identify your worst-performing DOM elements at the 75th percentile:

SQL
-- Query: Extract top 10 worst INP offenders by p75 latency in BigQuery
SELECT
  pageUrl,
  targetSelector,
  COUNT(1) AS sample_count,
  ROUND(APPROX_QUANTILES(value, 100)[OFFSET(75)], 1) AS p75_inp_ms,
  ROUND(APPROX_QUANTILES(value, 100)[OFFSET(95)], 1) AS p95_inp_ms,
  ROUND(AVG(subparts.inputDelay), 1) AS avg_input_delay_ms,
  ROUND(AVG(subparts.processingDuration), 1) AS avg_processing_ms,
  ROUND(AVG(subparts.presentationDelay), 1) AS avg_presentation_ms
FROM
  `your-project.telemetry.rum_inp_events`
WHERE
  timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
GROUP BY
  pageUrl,
  targetSelector
HAVING
  sample_count >= 100
ORDER BY
  p75_inp_ms DESC
LIMIT 10;

This query instantly reveals whether a button's 350ms INP is caused by Input Delay (main thread blocked by other scripts), Processing Duration (expensive click handler), or Presentation Delay (massive DOM reflow).


Field vs. Lab Discrepancies: Why Your Tests Differ from CrUX

A major source of developer confusion is the divergence between Synthetic Lab Testing (running Lighthouse or local Chrome DevTools) and Real User Field Telemetry (RUM and the Chrome User Experience Report / CrUX).

CharacteristicSynthetic Lab TestingReal User Measurement (RUM)
Testing DeviceHigh-end developer workstationDiverse budget smartphones ($100–$300 Androids)
Network ProfileHigh-speed fiber with simulated throttleUnpredictable cellular handoffs, packet loss, 3G
User InteractionScripted click or unattended loadReal human browsing, rapid taps, deep scrolling
Measurement TargetTotal Blocking Time (TBT) proxyReal Interaction to Next Paint (INP)
Google Search ImpactZero direct ranking impactDirect ranking signal evaluated by Google

A developer running a test on an Apple M3 MacBook will record a near-perfect Total Blocking Time of 20ms. However, when a real customer on a three-year-old budget mobile device taps your mobile navigation menu while the phone is thermal-throttling on a summer afternoon, that exact same menu tap triggers a 450ms INP stall.

Field monitoring is the only way to capture these real-world conditions.


How BugViso Complements RUM with Automated Deep Diagnostics

While RUM tells you that an interaction was slow in production, it rarely provides the deep architectural context needed to fix the underlying code. This is where BugViso's website scan bridges the gap between telemetry and code-level remediation.

BugViso incorporates a dedicated Speed, Performance & Simulation Engine alongside native React Hydration Diagnostics:

SYSTEM ARCHITECTURE & FLOW
+-----------------------------------------------------------------------------------+
|                        BUGVISO PERFORMANCE INTERCEPTION ARCHITECTURE              |
|                                                                                   |
|  [Headless Chromium Runner] ──> Automates page lifecycle via Playwright           |
|                                         │                                         |
|                                         ▼                                         |
|  [Vendored Web-Vitals Engine]──> Self-hosted web-vitals.iife.js captures metrics  |
|  [Long Tasks / CPU Profiler] ──> Intercepts all tasks > 50ms via Performance API  |
|  [CDP Code Coverage]         ──> Tracks exact unused JS bytes per bundle (>40%)   |
|  [React Hydration Engine]    ──> Intercepts minified SSR hydration errors (#418)  |
|                                         │                                         |
|                                         ▼                                         |
|  [Remediation Playbook]      ──> Maps slow tasks directly to source code lines     |
+-----------------------------------------------------------------------------------+

1. Vendored Offline web-vitals Integration

BugViso bundles self-hosted client libraries (vendor/web-vitals.iife.js) directly into its scanning container. During every crawl, it captures native Core Web Vitals alongside fallback PerformanceObserver metrics without relying on third-party CDNs.

2. Main-Thread Long Task Attribution

BugViso captures every main-thread Long Task (>50ms) using the W3C Long Tasks API. It calculates Total Blocking Time and automatically attributes the worst CPU offenders to their specific source JavaScript files and third-party vendor scripts.

3. React/Next.js Hydration Mismatch Interception

Hydration mismatches are one of the most common causes of high Input Delay on mobile frameworks. When React re-renders a component tree to fix an SSR discrepancy, it locks the main thread for hundreds of milliseconds. BugViso intercepts live console streams, parsing React minified codes (#418, #423, #425) and unminified text signatures to alert developers before deployment.

4. Consolidated Remediation Playbook

BugViso correlates synthetic CPU execution traces with actionable fixes, giving engineering teams prioritized, numbered code solutions to eliminate main-thread bottlenecks.


4 Production Traps in RUM Implementation

When rolling out production INP tracking, avoid these common implementation mistakes:

1. Sending RUM Data on Every Single Interaction

Never dispatch a network fetch on every click or keypress! If a user types 50 characters into a search field, firing 50 network requests will saturate the browser connection pool and worsen the very INP latency you are attempting to measure. Always use { reportAllChanges: false } or buffer interaction events locally and transmit only the final session INP upon page unload via navigator.sendBeacon().

2. Forgetting the navigator.sendBeacon Fallback

sendBeacon() is the gold standard for transmitting telemetry during page unloads. However, older webviews or restrictive corporate firewalls can occasionally block beacon payloads. Always implement a defensive fallback using fetch() with the keepalive: true flag.

3. Sampling at 100% on Ultra-High-Traffic Sites

If your web application receives 100 million page views per month, capturing 100% of interaction events will generate massive cloud ingestion bills. Implement dynamic sampling (e.g., capture 10% of sessions for desktop, and 25% of sessions for mobile where performance variability is highest):

TYPESCRIPT
// Sample 25% of mobile sessions and 10% of desktop sessions
const isMobile = /Android|iPhone|iPad/i.test(navigator.userAgent);
const sampleRate = isMobile ? 0.25 : 0.10;

if (Math.random() <= sampleRate) {
  initializeProductionINPMonitoring('/api/v1/telemetry/inp');
}

4. Ignoring navigationType in Analysis

When triaging high INP scores, always filter by navigationType. Back/Forward cache (bfcache) restores usually have zero input delay, while cold navigations have higher initial input delays. Segmenting your RUM data by navigation type prevents skewed diagnostic conclusions.


Frequently Asked Questions

How does Google determine my site's INP score for SEO rankings?

Google evaluates your site's INP using the Chrome User Experience Report (CrUX), which aggregates real-world field telemetry from millions of opted-in Chrome users over a rolling 28-day window. To earn a "Good" rating, at least 75% of all recorded user interactions across your URLs must resolve in ≤ 200 milliseconds.

What is the difference between Total Blocking Time (TBT) and INP?

  • Total Blocking Time (TBT): A synthetic laboratory metric that sums the blocking portions of all tasks exceeding 50ms during page load. It is measured in a simulated environment without real human interaction.
  • Interaction to Next Paint (INP): A Real User Measurement (RUM) metric that measures the actual elapsed time from user input to next visual frame across live sessions. Reducing lab TBT strongly correlates with lower field INP.

Can third-party chat widgets cause high INP scores on my buttons?

Yes. JavaScript executes on a single main thread. If a third-party customer support widget (such as Intercom or Zendesk) is running a heavy 200ms polling loop, and a user clicks your "Checkout" button during that loop, the button click cannot be processed until the chat script yields the CPU. The user experiences an input delay of 200ms+, causing an INP violation.

Why does my INP score show "needs-improvement" on mobile while desktop is green?

Mobile devices run on power-constrained mobile ARM processors that take 3x to 5x longer to execute JavaScript than desktop processors. Furthermore, mobile interactions frequently trigger touch-event emulation overhead and complex mobile viewport recalculations, inflating both Input Delay and Presentation Delay.

Is web-vitals safe to include in production bundles?

Yes. The official web-vitals library is extremely lightweight (less than 2KB gzipped) and uses modern, non-blocking browser APIs (PerformanceObserver) that run asynchronously off the main thread. It imposes negligible computational overhead on your application.


Summary

Achieving sustained Core Web Vitals excellence requires visibility into real user sessions: embed web-vitals to capture the three-phase interaction breakdown, stream high-resolution attribution to BigQuery or GA4, optimize p75 mobile interactions, and complement live field telemetry with automated synthetic execution profiling, which is precisely what an automated BugViso performance scan evaluates under rigorous real-world network and CPU throttling.

See where your site stands — free.