What events can we use when a website is loading? In-depth view of CRP (Critical Rendering Path)
Load lifecycle events: DOMContentLoaded (DOM parsed, before images/stylesheets finish), load (everything including subresources done), beforeunload/unload/pagehide (leaving), readystatechange. Plus the modern way: Performance API / PerformanceObserver and Core Web Vitals (LCP, FCP, CLS) for real measurement.
There are the classic lifecycle events and the modern measurement APIs — a strong answer covers both.
The classic load lifecycle events
DOMContentLoaded— fires when the HTML is fully parsed and the DOM is built — but before images, stylesheets, and subframes necessarily finish loading. This is when it's safe to query/manipulate the DOM. (Deferred scripts run just before this.) It's the event you usually want for "start my app."load(onwindow) — fires when everything is done: the DOM plus all subresources — images, CSS, fonts, iframes. Later thanDOMContentLoaded. Use it when you genuinely need all assets (e.g. measuring final layout).readystatechange+document.readyState—loading→interactive(≈ DOMContentLoaded) →complete(≈ load).- Leaving the page:
beforeunload(can prompt "unsaved changes"),pagehide,unload(unload is unreliable —pagehide/visibilitychangeare preferred for cleanup/beacons). visibilitychange— tab backgrounded/foregrounded; the modern, reliable hook for "user is leaving" (send analytics beacons here).
The modern way: actually measure the CRP
Lifecycle events tell you when phases finish; the Performance APIs tell you how the CRP performed:
performance.timing/ Navigation Timing /performance.getEntriesByType("navigation")— precise timestamps for DNS, TCP, TLS, request, response, DOM processing, etc.PerformanceObserver— subscribe to performance entries as they happen.- Paint Timing —
first-paintand First Contentful Paint (FCP). - Core Web Vitals, observed via
PerformanceObserver: - LCP (Largest Contentful Paint) — when the main content rendered.
- CLS (Cumulative Layout Shift) — visual stability.
- INP (Interaction to Next Paint) — responsiveness.
- Resource Timing — per-asset load timings.
Tying it to the CRP
The CRP is HTML→DOM→CSSOM→render tree→layout→paint→composite. The events map onto it: DOMContentLoaded ≈ "DOM built," FCP ≈ "first paint happened," LCP ≈ "main content painted," load ≈ "all subresources done." To optimize the CRP you measure with these APIs, then reduce render-blocking CSS, defer JS, etc.
The framing
"Two layers. Classic lifecycle events: DOMContentLoaded when the DOM is parsed — before images/CSS finish — which is when it's safe to touch the DOM and usually when you start your app; load when everything including subresources is done; readystatechange, and pagehide/visibilitychange for cleanup and beacons since unload is unreliable. But the modern, more useful layer is measurement: the Performance API and PerformanceObserver give you Navigation Timing, FCP, and Core Web Vitals — LCP, CLS, INP — which is how you actually quantify and then optimize the Critical Rendering Path."
Follow-up questions
- •What's the difference between DOMContentLoaded and load?
- •Why is the unload event unreliable, and what do you use instead?
- •How do you measure FCP and LCP programmatically?
- •How do the load events map onto the stages of the CRP?
Common mistakes
- •Confusing DOMContentLoaded (DOM ready) with load (everything ready).
- •Relying on the unload event for cleanup/analytics.
- •Only naming classic events, missing the Performance API entirely.
- •Not connecting the events to the CRP stages.
Performance considerations
- •The Performance APIs are how you instrument real-user monitoring (RUM) of the CRP — FCP, LCP, CLS, INP, navigation timing. Listening on the right events (visibilitychange over unload) ensures beacons actually send.
Edge cases
- •Deferred/async scripts and their timing relative to DOMContentLoaded.
- •bfcache (back-forward cache) and pageshow/pagehide.
- •Slow subresources delaying load long after the page is usable.
- •SPA route changes — none of these fire; you measure differently.
Real-world examples
- •web-vitals library reporting LCP/CLS/INP via PerformanceObserver to analytics.
- •Sending an analytics beacon on visibilitychange instead of unload.