{% extends "base.html" %} {% block title %}{{ s.name }} · RUM{% endblock %} {% macro rating_color(r) %}{% if r == 'good' %}var(--ok){% elif r == 'needs' %}var(--warn){% elif r == 'poor' %}var(--err){% else %}var(--text){% endif %}{% endmacro %} {% macro rating_label(r) %}{% if r == 'good' %}good{% elif r == 'needs' %}needs work{% elif r == 'poor' %}poor{% else %}no data{% endif %}{% endmacro %} {% block content %}
Real User Monitoring / {{ s.name }}

{% if s.last_seen_at %}{% else %}{% endif %}{{ s.name }} {% if ov.share.total %}{{ ov.share.good_pct }}% good{% else %}no data{% endif %}

{% if s.origins %}{{ s.origins|join(', ') }}{% else %}any origin{% endif %} · sampling {{ s.sample_rate }}% of sessions · last beacon {{ s.last_seen_at|timeago }} · {{ plan.retention_days }}-day history
{% for r in ranges %}{{ r }}{% endfor %}
{% if not s.last_seen_at %}
No beacons yet. Add the snippet from the Install tab to your pages; the first page view shows up within a minute.
{% endif %}
Page views · {{ range }}
{{ ov.views }}
{{ ov.sessions }} session{{ 's' if ov.sessions != 1 }}
LCP p75
{{ ov.vitals_view[0].fmt }}
{{ rating_label(ov.vitals_view[0].rating) }} · good ≤ 2.5 s
JS errors
{{ ov.errors }}
{{ '%.1f'|format(100 * ov.errors / ov.views) if ov.views else '—' }}{% if ov.views %}% of views{% endif %}
Experience
{% if ov.share.total %}
{{ ov.share.good_pct }}%{{ ov.share.needs_pct }}%{{ ov.share.poor_pct }}%
good · needs work · poor (by LCP)
{% else %}
no LCP samples yet
{% endif %}
Overview Pages {{ ov.pages|length }} JS errors{% if ov.error_rows %} {{ ov.error_rows|length }}{% endif %} Audience Resources Install Settings
{% if tab == 'overview' %}

Core Web Vitals · p75

Thresholds per web.dev: good · needs improvement · poor
{% for v in ov.vitals_view %}
{{ v.key|upper }}
{{ v.fmt }}
{{ v.label }}
{% set g = v.good %}{% set p = v.poor %}{% set scale = p * 1.5 %}{% set pos = ((v.value or 0) / scale * 100) if v.value is not none else 0 %}
{% if v.value is not none %}
{% endif %}
{{ rating_label(v.rating) }}≤ {{ g if v.key == 'cls' else (g ~ ' ms') }}
{% endfor %}
Page load p75 {{ '%.0f ms'|format(ov.load) if ov.load is not none else '—' }} · DOM ready p75 {{ '%.0f ms'|format(ov.dom_ready) if ov.dom_ready is not none else '—' }}{% if not ov.raw_only %} · p75 over {{ range }} is the view-weighted average of hourly p75s{% endif %}
Page views over time
{% if ov.series|length < 2 or ov.views == 0 %}

No page views in this range yet.

{% else %}
{{ ov.series[0].t[:16]|replace('T',' ') }} UTC views · sessions · errorsnow
{% endif %}
{% for p in ov.pages[:10] %}{% else %}{% endfor %}
Top pagesViewsLCP p75Errors
{{ p.path }}{{ p.views }}{{ p.lcp_fmt }}{{ p.errors }}{% if p.error_rate is not none and p.errors %} · {{ p.error_rate }}%{% endif %}
No page views yet
{% if ov.pages|length > 10 %}{% endif %}
{% for e in ov.error_rows[:8] %}{% else %}{% endfor %}
Top JS errorsCountLast seen
{{ e.message }}
{{ e.source or '' }}
{{ e.count }}{{ e.last_seen|timeago }}
No JavaScript errors in this range ✓
{% if ov.error_rows|length > 8 %}{% endif %}
{% elif tab == 'pages' %}
{% for p in ov.pages %} {% else %}{% endfor %}
PathViewsShareLCP p75ErrorsError rate
{{ p.path }}{{ p.views }}{{ '%.0f'|format(100 * p.views / ov.views) if ov.views else 0 }}%{{ p.lcp_fmt }} {{ rating_label(p.lcp_rating) if p.lcp is not none else '' }}{{ p.errors }}{{ p.error_rate if p.error_rate is not none else '—' }}{% if p.error_rate is not none %}%{% endif %}
No page views in this range yet.

Paths are normalised: query strings are dropped and numeric, hex and UUID segments become {id}, so /orders/1234 and /orders/5678 count as one page.

{% elif tab == 'errors' %} {% if not ov.error_rows %}

No JavaScript errors

Uncaught exceptions and unhandled promise rejections from your visitors' browsers show here, grouped by message and source file.

{% else %} {% for e in ov.error_rows %}
{{ e.count }}×{{ e.message }}
{{ e.source or 'unknown source' }} · top page {{ e.last_path or '—' }}
first {{ e.first_seen|timeago }} · last {{ e.last_seen|timeago }}
{{ e.last_stack or '(no stack trace captured)' }}
{% endfor %}{% endif %} {% elif tab == 'audience' %}

Breakdowns use raw page views from the last {{ raw_days }} days ({{ ov.breakdown_views }} views). Region is approximated from the browser's timezone and language, not the IP address.

{% for title, rows in [('Browser', ov.by_browser), ('Operating system', ov.by_os), ('Device type', ov.by_device), ('Region (approx.)', ov.by_region), ('Referrer host', ov.by_referrer)] %}

{{ title }}

{% for r in rows %}
{{ r.name }}{{ r.views }} · {{ r.pct }}%
{% else %}
No data yet
{% endfor %}
{% endfor %}
{% elif tab == 'resources' %}

Slowest third-party and first-party resources reported by the browser's Resource Timing API (top 20 per page view, last {{ raw_days }} days), grouped by host and type.

{% for r in ov.resources %} {% else %}{% endfor %}
HostTypeSamplesp75 durationAvg size
{{ r.host }}{{ r.type }}{{ r.count }}{{ '%.0f ms'|format(r.p75) if r.p75 is not none else '—' }}{% if r.avg_bytes %}{{ '%.1f KB'|format(r.avg_bytes / 1024) }}{% else %}— (opaque){% endif %}
No resource timings yet.

Cross-origin resources report a size only when the server sends Timing-Allow-Origin.

{% elif tab == 'install' %}

Add the snippet

Paste this before </head> (or anywhere) on every page. It loads async, weighs under 8 KB, sets no cookies and never throws into your page.

{{ snippet }}

What it collects

Options

data-sample="10" overrides the site's sample rate for that page. Beacons go to {{ base_url }}/rum/<token> with navigator.sendBeacon at page hide, errors immediately.

{% if user.can_edit %}
{% endif %}

Content Security Policy

If your site sets a CSP, allow the script and the beacon endpoint:

script-src {{ base_url.split('//')[-1] }};
connect-src {{ base_url.split('//')[-1] }};

Single-page apps

The snippet records one view per full page load. Route changes inside an SPA are attributed to the entry path; call window.vigilRum && window.vigilRum.view() after a route change to record a new view.

Verify

Open your site, navigate away, and refresh this page: the first page view appears within a minute. In DevTools › Network filter for /rum/ to see the beacon (a 204).

Allowed origins

{% if s.origins %}Only beacons with an Origin of {{ s.origins|join(', ') }} are accepted; others get 403.{% else %}Any origin is accepted. Restrict it under Settings so nobody can inflate your numbers with your token.{% endif %}

{% elif tab == 'settings' %}
Comma-separated. https://example.com matches exactly; example.com matches any scheme; *.example.com matches subdomains. Empty = any origin.
Browsers cache the rate for an hour. Counts on this page are not scaled up; multiply by 100 / rate for estimates.
{% if user.can_edit %}
{% endif %}
{% if user.can_edit %}
{% endif %} {% endif %} {% endblock %} {% block scripts %} {% if tab == 'overview' and ov.series|length >= 2 and ov.views %} {% endif %} {% endblock %}