{% 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 %}
No page views in this range yet.
{% else %}| Top pages | Views | LCP p75 | Errors |
|---|---|---|---|
| {{ 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 | |||
| Top JS errors | Count | Last seen |
|---|---|---|
{{ e.source or '' }} | {{ e.count }} | {{ e.last_seen|timeago }} |
| No JavaScript errors in this range ✓ | ||
| Path | Views | Share | LCP p75 | Errors | Error 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.
Uncaught exceptions and unhandled promise rejections from your visitors' browsers show here, grouped by message and source file.
{{ e.last_stack or '(no stack trace captured)' }}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.
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.
| Host | Type | Samples | p75 duration | Avg 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.
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 }}
window.onerror and unhandledrejection: message, file:line, stack up to 1 KB. Deduplicated per page, max 20 per page.sessionStorage for 30 minutes of inactivity. No cookies, no IP storage, no fingerprinting.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 your site sets a CSP, allow the script and the beacon endpoint:
script-src {{ base_url.split('//')[-1] }};
connect-src {{ base_url.split('//')[-1] }};
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.
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).
{% 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 %}