next.js icon indicating copy to clipboard operation
next.js copied to clipboard

`useReportWebVitals` (LCP) Intermittently Not Reporting When User is Idle

Open samtbg opened this issue 2 months ago • 2 comments

Link to the code that reproduces this issue

https://github.com/samtbg/userReportWebVitalsMissingLCP

To Reproduce

  1. Clone the linked repository and run the necessary package install command:
npm install --legacy-peer-deps
  1. Start the development server:
npm run dev
  1. Navigate to http://localhost:3000.

  2. Open the browser DevTools Console and clear the logs.

Test 1 (Failure Scenario - Idle User) This demonstrates the missing LCP report:

Reload the page.

Wait 12 seconds without moving or clicking the mouse.

Result: The console will show the "10-second timer fired" message. The OTel flush runs, and the log will confirm TTFB and FCP were sent, but LCP is missing.

Test 2 (Success Scenario - Interactive User) This demonstrates the required workaround:

Reload the page.

Wait 5 seconds (the LCP element is visible).

Click once anywhere on the white space. The console should show the LCP metric recorded immediately after the click.

Wait for the 10-second timer to fire (5 more seconds).

Result: The console will successfully show TTFB, FCP, and LCP being sent.

Current vs. Expected behavior

Current Behavior (Observable in the Console):

Test 1 (Failure): The console immediately logs the recording of TTFB and FCP. At approximately 10 seconds, the console logs the "10-second timer fired" and the OTel flush runs. The final flush log shows that LCP is missing from the buffer. The useReportWebVitals callback for LCP never executes.

Test 2 (Success): When a click on the page occurs, LCP is recorded and reported in the console

Expected Behaviour

The LCP is recorded/reported irregardless of user interaction

Provide environment information

From the reproduction:

Operating System:
  Platform: linux
  Arch: x64
  Version: #88-Ubuntu SMP PREEMPT_DYNAMIC Sat Oct 11 09:28:41 UTC 2025
  Available memory (MB): 39737
  Available CPU cores: 8
Binaries:
  Node: 20.11.0
  npm: 11.4.2
  Yarn: 1.22.22
  pnpm: N/A
Relevant Packages:
  next: 16.0.2-canary.24 // Latest available version is detected (16.0.2-canary.24).
  eslint-config-next: N/A
  react: 18.3.1
  react-dom: 18.3.1
  typescript: 5.4.5
Next.js Config:
  output: N/A

However we're seeing this behaviour in our NextJS apps (which have a wide range of versions)

Which area(s) are affected? (Select all that apply)

Performance

Which stage(s) are affected? (Select all that apply)

next dev (local)

Additional context

No response

samtbg avatar Nov 19 '25 11:11 samtbg

Fix: LCP Reporting for Idle Users

Problem

The useReportWebVitals hook was intermittently not reporting LCP (Largest Contentful Paint) metric when users were idle. According to issue #86291, LCP was only reported after user interaction (click, mouse movement, etc.), but not when users remained inactive on the page.

Root Cause

The web-vitals library doesn't finalize LCP metric until certain events occur. For idle users who don't interact with the page, the LCP metric never gets finalized and reported, leading to incomplete performance data.

Solution

Added event handlers for visibilitychange and pagehide events to finalize and report LCP metric when:

  • The page visibility state changes to hidden
  • The page is being unloaded (pagehide event)

Implementation Details

  1. LCP Tracking: Uses performance.getEntriesByType('largest-contentful-paint') to get the last LCP entry
  2. Finalization Logic: When page becomes hidden or unloads, extracts the LCP value from the performance entry
  3. Duplicate Prevention: Tracks if LCP was already reported via the normal web-vitals flow to prevent duplicate reporting
  4. Metric Construction: Creates a proper Metric object with:
    • renderTime or loadTime from the LCP entry (preferred)
    • Falls back to startTime if renderTime/loadTime are not available
    • Proper rating calculation (good/needs-improvement/poor)
    • Normalized navigation type

Code Changes

File: packages/next/src/client/web-vitals.ts

  • Added lcpReportedRef to track if LCP was already reported
  • Added finalizeLCP() function that:
    • Checks if LCP was already reported
    • Gets the last LCP entry from Performance API
    • Constructs and reports the LCP metric
  • Added event listeners for visibilitychange and pagehide
  • Wrapped the web-vitals callback to track when LCP is reported normally

Testing

Manual Testing

  1. Idle User Scenario:

    # Start dev server
    npm run dev
    
    # Navigate to a page with useReportWebVitals
    # Wait 10+ seconds without interacting
    # Check console/logs - LCP should be reported when page becomes hidden
    
  2. Interactive User Scenario (should still work):

    # Navigate to page
    # Interact with page (click, scroll)
    # LCP should be reported normally via web-vitals
    

Automated Testing

File: test/e2e/app-dir/app/useReportWebVitals.test.ts

Added test case: should report LCP for idle users when page becomes hidden

The test:

  1. Sets up a page with useReportWebVitals
  2. Waits for page to load
  3. Simulates visibilitychange event with hidden state
  4. Verifies that LCP metric was reported

Run the test:

npm test -- test/e2e/app-dir/app/useReportWebVitals.test.ts

Test Scenarios Covered

  • ✅ LCP reported for idle users when page becomes hidden
  • ✅ LCP reported on pagehide event
  • ✅ No duplicate reporting when LCP is already reported normally
  • ✅ Existing functionality (interactive users) still works

Files Changed

  1. packages/next/src/client/web-vitals.ts - Main implementation
  2. test/e2e/app-dir/app/useReportWebVitals.test.ts - Test coverage

Related Issue

Fixes #86291: useReportWebVitals (LCP) Intermittently Not Reporting When User is Idle

rosbitskyy avatar Dec 01 '25 16:12 rosbitskyy

😍 Thank you so much for the detailed response! We can change our implementation to push metrics on those events!

Do you know if we're waiting on an official release for the MR you raised before we can test it out?

Thanks, Sam

samtbg avatar Dec 05 '25 12:12 samtbg