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 byview_loading_type = "initial_load"; - When analyzing FCP/LCP for initial load, filter by
initial_load; when analyzing FCP/LCP for SPA routes, filter byroute_changeand 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_changethat 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_shiftis 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:
first_byteis high: Prioritize checking server-side response, redirects, CDN, geography, and network;first_byteis normal butfirst_contentful_paintis high: Prioritize checking render-blocking CSS, JavaScript, and client-side initialization;first_contentful_paintis normal butlargest_contentful_paintis high: Prioritize checking whether the LCP element's resource was discovered late, downloaded slowly, or rendered with delay;loading_timeis 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_taskfieldsduration,blocking_duration, andscripts: 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:
view_path_group: First identify the affected pages;version: Determine if it is a regression caused by a new version;browser,browser_version_major: Determine browser compatibility or capability differences;device,screen_size: Distinguish between device performance and layout differences;network_type: Distinguish between Wi-Fi, cellular, and unreachable networks;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:
- 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; - Missing FCP candidates: A finished initial load View with a stay time > 3 seconds, but
first_contentful_paintdoes 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_changein 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_byteis also high: Prioritize checking server, CDN, network, and redirects;first_byteis 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: Usesession_idorview_idto 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:
- P75 trend and good rate for LCP, INP, and CLS;
- P75 trend for FCP,
first_byte, andloading_time; - Top 20 slow pages sorted by
view_path_group; - Pre-release vs. post-release trend compared by
version; - Tail experience broken down by
browser,device,network_type, and geography; - Correlation chart of
view_error_count,view_long_task_count,frustration_countwith performance metrics; - 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.