Skip to content

Web Page Performance and User Experience Analysis


The Web RUM SDK correlates the loading, rendering, interaction, layout shifts, errors, resources, and long task data of real users visiting a page into a single view_id. After application data is reported to TrueWatch, you can first use Core Web Vitals to assess the overall experience, then drill down by page, version, browser, device, and network dimensions to locate the affected scope, and finally investigate errors, resources, long tasks, and Session Replay along the view_id.

All field names in this document come from the current data structure reported by the Web RUM SDK. For the full list of fields, see Web Application Data Collection.

Clarify the Statistics Scope Before Analysis

Distinguish Between Initial Load and Route Change

view_loading_type distinguishes two types of Views:

  • initial_load: The browser is opened for the first time or the page is refreshed;
  • route_change: A route change occurs in a single-page application (SPA).

first_byte, first_input_delay, and document navigation timing fields are only collected on initial_load Views. loading_time, interaction_to_next_paint, cumulative_layout_shift, and View-related counts apply to both initial_load and route_change.

Starting from RUM SDK 3.3.9, for SPAs, Chrome browsers that support the native Soft Navigation API can collect first_contentful_paint, largest_contentful_paint, and largest_contentful_paint_element_selector on route_change Views, and mark the view_navigation_type as push, replace, or traverse. Browsers that do not support the native API still create Route Views, and retain a recognizable navigation type through Navigation API, History API, or Hash routing compatibility logic, but this route typically does not have FCP/LCP.

Therefore:

  • When analyzing above-the-fold content, white screen candidates, and TTFB, you must filter by view_loading_type = "initial_load";
  • When analyzing FCP/LCP for initial load, filter by initial_load; when analyzing FCP/LCP for SPA routes, filter by route_change and only samples where the metric exists and the browser supports the native Soft Navigation API;
  • When analyzing SPA route performance, you should also combine fields such as loading_time, interaction_to_next_paint, cumulative_layout_shift, errors, and long tasks;
  • Do not treat records in route_change that lack FCP or LCP as zero values or anomalies.

Use P75, Not Just Averages

Core Web Vitals recommends using the 75th percentile (P75) of page view samples for evaluation, and observing mobile and desktop separately. A passing P75 indicates that at least 75% of page visits are within the target range. Averages can be easily diluted by many fast samples and do not represent the true experience of slow users.

It is recommended to also observe:

  • P50: Typical user experience;
  • P75: Primary Core Web Vitals evaluation metric;
  • P90 or P95: Tail user experience for slow networks, low-performance devices, etc.;
  • Good rate: Proportion of Views within the "good" threshold out of valid samples;
  • Sample size: Avoid judging page or version quality based on a small number of samples.

For metric definitions, thresholds, and P75 calculation methods, refer to web.dev: Web Vitals and Best Practices for Real-World Web Vitals Statistics.

Note the Units

  • Except for cumulative_layout_shift, all time metrics listed in this document are stored in nanoseconds (ns) in the data;
  • 1s = 1,000,000,000ns, 1ms = 1,000,000ns;
  • cumulative_layout_shift is a unitless fraction; do not perform time conversion;
  • DQL thresholds must use the original storage unit. For example, LCP of 2.5 seconds should be written as 2500000000.

Core Experience Metrics

Core Web Vitals

Experience Dimension SDK Field Valid View Good Needs Improvement Poor Targeting Field
Largest Contentful Paint (LCP) largest_contentful_paint initial_load, route_change with native Soft Navigation API support ≤ 2.5s > 2.5s and ≤ 4s > 4s largest_contentful_paint_element_selector
Interaction to Next Paint (INP) interaction_to_next_paint initial_load, route_change ≤ 200ms > 200ms and ≤ 500ms > 500ms interaction_to_next_paint_target_selector
Cumulative Layout Shift (CLS) cumulative_layout_shift initial_load, route_change ≤ 0.1 > 0.1 and ≤ 0.25 > 0.25 cumulative_layout_shift_target_selector

The thresholds for LCP, INP, and CLS refer to LCP, INP, and CLS respectively.

LCP: When the User Sees the Main Content

largest_contentful_paint indicates the time when the largest image, text block, or video within the viewport finishes rendering. The SDK also reports largest_contentful_paint_element_selector, which can be used to locate the page element that produces the LCP.

When LCP is poor, use the following fields to determine which stage consumes the time:

  1. first_byte is high: Prioritize checking server-side response, redirects, CDN, geography, and network;
  2. first_byte is normal but first_contentful_paint is high: Prioritize checking render-blocking CSS, JavaScript, and client-side initialization;
  3. first_contentful_paint is normal but largest_contentful_paint is high: Prioritize checking whether the LCP element's resource was discovered late, downloaded slowly, or rendered with delay;
  4. loading_time is still significantly higher than LCP: The main content of the page is already visible, but there are ongoing network requests or DOM changes.

INP: Whether the Page Responds to Actions in a Timely Manner

interaction_to_next_paint measures the total delay from a single interaction — from input, through event handling, to the next frame being painted. The SDK continuously observes interactions within a View and reports the value close to the worst interaction, while also identifying the corresponding element via interaction_to_next_paint_target_selector.

When INP is poor, focus on correlating:

  • view_long_task_count: Whether there are tasks blocking the main thread for more than 50ms;
  • view_error_count: Whether script errors occurred during the interaction;
  • frustration_count: Whether there were frustrating behaviors such as repeated clicks, dead clicks, or error clicks;
  • R::long_task fields duration, blocking_duration, and scripts: Locate blocking time and related scripts.

Views without user interaction may not have interaction_to_next_paint. When analyzing INP, only use samples where this field exists.

CLS: Whether the Page Content Moves Unexpectedly

cumulative_layout_shift measures unexpected movement of visible page elements, and cumulative_layout_shift_target_selector points to the element that caused the greatest impact.

When CLS is poor, common directions to check include:

  • Images, videos, ads, or iframes without explicit dimensions;
  • Asynchronous content inserted above existing content;
  • Web font swaps causing text size changes;
  • Animations implemented using properties that trigger layout.

Initial Load Supplementary Metrics

SDK Field Description Suggested Use
first_byte Time from navigation start to the first byte of the document response Determine if the server, network, redirects, or CDN are slowing down subsequent rendering
first_contentful_paint Time when the first text, image, SVG, or non-white canvas is painted Measures when the user first sees content; also the primary field for white screen candidate analysis
dom_interactive Time when the document parser finishes parsing Determine if HTML and synchronous scripts delay the DOM interactive state
dom_content_loaded Time when DOMContentLoaded completes Determine when DOM construction and deferred scripts finish
dom_complete Time when the page and all subresources have completed Determine if the full document lifecycle is too long
load_event Time when the load event completes Determine when all dependent resources have finished loading
first_paint_time responseEnd - fetchStart as calculated by the current SDK Observe the duration of the document response phase; do not equate it to FCP
time_to_interactive domInteractive - fetchStart as calculated by the current SDK Observe the time until the DOM reaches an interactive state; do not confuse with lab TTI
dom_ready domContentLoadedEventEnd - fetchStart as calculated by the current SDK Observe the time to complete DOM Ready
resource_load_time loadEventStart - domContentLoadedEventEnd as calculated by the current SDK Observe the time spent waiting for dependent resources after DOM Ready
dom domComplete - domInteractive as calculated by the current SDK Observe the time from DOM interactive to complete

For FCP, refer to web.dev: FCP: good is ≤ 1.8 seconds, poor is > 3 seconds. For first_byte, refer to web.dev: Time to First Byte: ≤ 0.8 seconds is typically good, > 1.8 seconds is typically poor; TTFB is not a Core Web Vital and should be evaluated together with FCP and LCP.

Page Load and Usage Experience Metrics

SDK Field Description Questions It Answers
loading_time The time the page stabilizes, as determined by the SDK based on the load event, network requests, and DOM activity When does the page finish its main loading activity; whether SPA route changes involve ongoing requests or DOM updates
time_spent The time the user spends on the current View Whether a slow page causes users to leave quickly; whether anomalies affect long-term usage
view_error_count Number of errors associated with the current View Whether the page is unusable due to script exceptions
view_resource_count Number of resource requests associated with the current View Whether the page has too many requests; whether a resource waterfall analysis is needed
view_long_task_count Number of long tasks associated with the current View Whether the main thread is frequently blocked
view_action_count Number of user actions associated with the current View Whether the page is actually being used; whether the absence of INP samples is due to no interaction
frustration_count Number of frustrating behaviors associated with the current View Whether users are repeating operations due to unresponsiveness, no results, or errors
is_active Whether the View is still active Filter out Views that have not ended and whose metrics may still be updated
session_has_replay Whether the current session has an associated Session Replay Whether you can directly replay the scene of the anomalous user

loading_time is not equivalent to the time of visual completion. A page can complete LCP first, but background polling, API requests, or ongoing DOM updates can make loading_time longer; conversely, a normal loading_time does not prove that the page's main content is correctly displayed.

Establish a Page Performance Baseline

Step 1: View P75 for Each Page

The following queries are intended for dashboard charts, so they intentionally avoid writing fixed time ranges like [24h] or [1h] in the DQL. Omitting the time range allows the query to follow the time widget in the upper right corner of the dashboard; if a time range is specified directly in the DQL, its priority is higher than the dashboard time widget, causing the chart to query that fixed time period. For more details, see DQL Time Range Specification. Replace <APP_ID> with your application ID.

View LCP P75 by page:

R::view:(percentile(largest_contentful_paint, 75) AS p75_lcp) {app_id = "<APP_ID>", view_loading_type = "initial_load", largest_contentful_paint = exists()} BY view_path_group SORDER BY p75_lcp DESC SLIMIT 20

View INP P75 by page:

R::view:(percentile(interaction_to_next_paint, 75) AS p75_inp) {app_id = "<APP_ID>", interaction_to_next_paint = exists()} BY view_path_group SORDER BY p75_inp DESC SLIMIT 20

View CLS P75 by page:

R::view:(percentile(cumulative_layout_shift, 75) AS p75_cls) {app_id = "<APP_ID>", cumulative_layout_shift = exists()} BY view_path_group SORDER BY p75_cls DESC SLIMIT 20

Do not directly add the three metrics together to form a single score. LCP, INP, and CLS represent loading, responsiveness, and visual stability respectively; any one being poor requires separate investigation.

Step 2: Observe the Good Rate

The following query calculates the proportion of valid Views with LCP ≤ 2.5 seconds:

eval(100 * A / B, alias='LCP_good_rate', A="R::view:(count(view_id)) {app_id = '<APP_ID>', view_loading_type = 'initial_load', largest_contentful_paint <= 2500000000}", B="R::view:(count(view_id)) {app_id = '<APP_ID>', view_loading_type = 'initial_load', largest_contentful_paint = exists()}")

Similarly, you can replace the conditions with:

  • INP good: interaction_to_next_paint <= 200000000;
  • CLS good: cumulative_layout_shift <= 0.1;
  • FCP good: first_contentful_paint <= 1800000000.

The denominator must only count valid samples where the metric exists; otherwise, browsers that do not support the metric, Views without interaction, or Views that were left early will distort the results.

Step 3: Break Down by Key Dimensions

It is recommended to use the following SDK fields for grouping or filtering, in this order:

  1. view_path_group: First identify the affected pages;
  2. version: Determine if it is a regression caused by a new version;
  3. browser, browser_version_major: Determine browser compatibility or capability differences;
  4. device, screen_size: Distinguish between device performance and layout differences;
  5. network_type: Distinguish between Wi-Fi, cellular, and unreachable networks;
  6. env, service: Avoid mixing data from different environments or services.

For example, compare Core Web Vitals by version and page:

R::view:(percentile(largest_contentful_paint, 75) AS p75_lcp, percentile(interaction_to_next_paint, 75) AS p75_inp, percentile(cumulative_layout_shift, 75) AS p75_cls) {app_id = "<APP_ID>"} BY version, view_path_group SORDER BY p75_lcp DESC SLIMIT 50

Since LCP has different measurement scopes in initial_load and route_change with native Soft Navigation API support, and INP may be missing due to no interaction, it is recommended in production dashboards to create separate charts for each of the three metrics, and separate LCP statistics for initial load and SPA routes, then use the same page, version, and device filters for linked analysis.

White Screen and Long No-Content Analysis

White Screen Is Not a Direct SDK Metric

The current SDK does not report a field named white_screen. White screen can only be approximated by constructing candidate sets from collected metrics, and then confirmed by combining errors, resources, long tasks, and Session Replay.

It is recommended to divide candidates into two levels:

  1. Slow FCP candidates: first_contentful_paint > 3000000000. This means the user did not see the first content until more than 3 seconds, which is a relatively stable and measurable approximation of the white screen experience;
  2. Missing FCP candidates: A finished initial load View with a stay time > 3 seconds, but first_contentful_paint does not exist. This rule can only be used for investigation and cannot be directly equated to a white screen.

Missing FCP can also be caused by:

  • The current browser does not support the FCP collection capability used by the SDK;
  • The page goes into the background or is hidden before FCP is produced;
  • The user closes the page early;
  • Data sampling, network reporting, or the page lifecycle ends prematurely;
  • Incorrectly including route_change in the initial load white screen analysis.

Therefore, the missing FCP rule must filter by view_loading_type = "initial_load" and verify the collection coverage by browser. For browser capability boundaries, see Browser Support.

Calculate the Slow FCP Candidate Rate

eval(100 * A / B, alias='white_screen_candidate_rate', A="R::view:(count(view_id)) {app_id = '<APP_ID>', view_loading_type = 'initial_load', first_contentful_paint > 3000000000}", B="R::view:(count(view_id)) {app_id = '<APP_ID>', view_loading_type = 'initial_load', first_contentful_paint = exists()}")

This rate indicates "the proportion of valid FCP samples where the user saw content after more than 3 seconds", and does not represent the true white screen rate confirmed by visual inspection. It is recommended to break down further by view_path_group, version, browser, and network_type.

Find Slow FCP Views

R::view:(view_id, session_id, view_url, first_contentful_paint, first_byte, loading_time, view_error_count, view_resource_count, view_long_task_count, session_has_replay) {app_id = "<APP_ID>", view_loading_type = "initial_load", first_contentful_paint > 3000000000} ORDER BY first_contentful_paint DESC LIMIT 100

Interpretation of results:

  • first_byte is also high: Prioritize checking server, CDN, network, and redirects;
  • first_byte is normal but FCP is high: Prioritize checking render-blocking resources, client-side rendering, and synchronous JavaScript;
  • view_error_count > 0: Prioritize reviewing the error stack to confirm if initialization was interrupted;
  • view_long_task_count > 0: Check if the main thread was blocked by long tasks before the first paint;
  • session_has_replay = true: Use session_id or view_id to open Session Replay and confirm the actual screen the user saw.

Find Missing FCP Candidates

R::view:(view_id, session_id, view_url, time_spent, view_error_count, view_resource_count, view_long_task_count, session_has_replay) {app_id = "<APP_ID>", view_loading_type = "initial_load", is_active = false, time_spent > 3000000000, first_contentful_paint != exists()} ORDER BY time_spent DESC LIMIT 100

First, filter out samples from browsers that do not support FCP by browser, then confirm using Session Replay and drill-down queries. Do not create a "true white screen rate" alert based solely on missing FCP.

Drill Down to the Root Cause by view_id

Substitute <VIEW_ID> from the candidate results into the following queries.

View script and runtime errors:

R::error:(error_type, error_source, error_message, error_stack) {app_id = "<APP_ID>", view_id = "<VIEW_ID>"} ORDER BY time ASC LIMIT 100

View failed or slow resources:

R::resource:(resource_url, resource_type, resource_status, duration, resource_ttfb, resource_trans) {app_id = "<APP_ID>", view_id = "<VIEW_ID>", resource_status >= 400} ORDER BY duration DESC LIMIT 100

If there are no HTTP errors but you still suspect slow resources, you can remove resource_status >= 400 and view the slowest resources by duration. A high resource_ttfb usually indicates server-side or network waiting, while a high resource_trans usually indicates content transfer time.

View long tasks:

R::long_task:(duration, blocking_duration, scripts) {app_id = "<APP_ID>", view_id = "<VIEW_ID>"} ORDER BY duration DESC LIMIT 100

duration indicates the total duration of the long task, blocking_duration indicates the portion that blocks user input, and scripts can be used to locate the related scripts.

Common Experience Issue Diagnosis Paths

Symptom First Check Fields Then Correlate Common Directions
Page has no content for a long time first_contentful_paint, first_byte view_error_count, view_long_task_count, Resource Slow server, render blocking, initialization errors, long tasks
Main content loads slowly largest_contentful_paint largest_contentful_paint_element_selector, Resource LCP resource discovered late, downloaded slowly, rendered with delay
No response after clicking interaction_to_next_paint interaction_to_next_paint_target_selector, Long Task, Error Main thread blocked, heavy event handling, script errors
Page content jumps cumulative_layout_shift cumulative_layout_shift_target_selector Missing dimensions, dynamic insertion, fonts and animations
Page keeps showing loading indicator loading_time Resource, DOM activity, Error Unfinished requests, polling, ongoing DOM updates, unhandled abnormal states
User clicks repeatedly or clicks yield no result frustration_count Action, INP, Error, Session Replay No visual feedback, dead clicks, error clicks, slow interactions
New version experience degrades version + P75 view_path_group, browser, network_type Resource size, code path, compatibility issues, or release regression

Dashboard and Alert Recommendations

It is recommended to create at least the following views:

  1. P75 trend and good rate for LCP, INP, and CLS;
  2. P75 trend for FCP, first_byte, and loading_time;
  3. Top 20 slow pages sorted by view_path_group;
  4. Pre-release vs. post-release trend compared by version;
  5. Tail experience broken down by browser, device, network_type, and geography;
  6. Correlation chart of view_error_count, view_long_task_count, frustration_count with performance metrics;
  7. Slow FCP candidate rate and list of replayable samples.

Alerts should prioritize using P75 or the good rate, and set a minimum sample size and consecutive trigger conditions to avoid false alarms caused by a small number of anomalous samples or short-term traffic fluctuations. It is recommended to start with the "poor" threshold of Core Web Vitals, and then adjust based on the importance of the business page, historical baseline, and actual traffic.

Perform Analysis in the Console

  • View overview, page performance, resource, and error analysis under Real User Monitoring > Analysis Dashboard > Web;
  • Under Real User Monitoring > Explorer, filter raw Views by view_id, session_id, page, version, and browser;
  • After entering a single View, inspect associated Resource, Error, Long Task, Action, and Session Replay;
  • When custom statistics are needed, use the DQL in this document to create dashboard charts or Real User Detection monitors.

For more console capabilities, see Analysis Dashboard, Explorer, and Real User Detection.