LoAF vs the Long Tasks API: Which Observer Should Feed Your INP Debugging?
This comparison belongs to the Long Animation Frames topic in Core Web Vitals & Measurement. Both APIs report main-thread congestion above the 50ms threshold, and many RUM setups still collect longtask entries out of habit. The question is whether that data can still earn its beacon bytes now that long-animation-frame exists.
The short answer: for diagnosing Interaction to Next Paint, LoAF supersedes Long Tasks in Chromium. The longer answer depends on what else you use task data for — Total Blocking Time approximations, cross-browser coverage plans, or historical dashboards you do not want to break.
Rapid Diagnosis: Is Your Current Data Actionable?
- Inspect a week of
longtaskattribution. If more than 90% of entries carryname: "self"and a container ofwindow, your long-task data identifies that the thread was busy and nothing more. - Look for slow interactions without long tasks. An INP of
260mscomposed of five40mstasks produces nolongtaskentries at all — but one LoAF entry. - Check whether presentation delay is ever attributed. Long Tasks cannot see style, layout and paint after your script returns; if your INP breakdown shows presentation delay dominating, you have been blind to its cause.
- Count browsers. If a large share of your RUM comes from Safari and Firefox, neither API covers it; the choice between them is a Chromium-only decision.
The Comparison
| Axis | Long Tasks API | Long Animation Frames API |
|---|---|---|
| Unit | One task over 50ms | One frame over 50ms |
| Script attribution | Container only, usually unhelpful | URL, function, char position, invoker |
| Rendering visibility | None | renderStart, styleAndLayoutStart |
| Forced layout | Not reported | Per script |
| Many short tasks | Missed entirely | Captured as one long frame |
| Blocking metric | Duration over 50ms per task | blockingDuration per frame |
| Support | Chromium | Chromium 123+ |
Root Cause Analysis: Why Long Tasks Misled Teams
1. The task is the wrong unit for an interaction. A user experiences the delay between input and the next paint. That span can include several tasks and a rendering step. A metric that only fires when one task exceeds 50ms systematically misses the "death by several cuts" pattern common in framework apps, where each microtask batch is modest.
2. Attribution was structurally limited. Long Tasks attribute to a container — the frame or iframe — not to a script. For first-party work in the top frame, that is always "the page". Teams built elaborate timestamp-correlation heuristics to compensate; LoAF makes them unnecessary.
3. Rendering was invisible. A handler that returns in 20ms but invalidates layout for 10,000 nodes produces no long task; the expensive work happens in the browser's rendering step. LoAF reports it as the render tail of the frame.
Step-by-Step Migration
1. Add a LoAF observer next to the existing one
Run both for a release so you can compare coverage before deleting anything.
const supportsLoAF = PerformanceObserver.supportedEntryTypes?.includes('long-animation-frame');
if (supportsLoAF) {
new PerformanceObserver((l) => queueLoAF(l.getEntries()))
.observe({ type: 'long-animation-frame', buffered: true });
} else {
new PerformanceObserver((l) => queueLongTasks(l.getEntries()))
.observe({ type: 'longtask', buffered: true });
}
// trade-off: falling back to longtask on non-LoAF engines gains nothing today —
// the engines without LoAF also lack longtask. The branch only helps older
// Chromium builds; drop it once their share of your traffic is negligible.
Expected outcome: within a day, you can see how many slow interactions had zero longtask entries but a LoAF entry — usually a meaningful share on framework-heavy pages.
2. Rebuild your blocking-time approximation on blockingDuration
If dashboards chart a field "TBT-like" figure from long tasks, switch to summing blockingDuration across frames between FCP and a cutoff.
let fieldBlocking = 0;
function queueLoAF(entries) {
for (const f of entries) if (f.startTime < 10_000) fieldBlocking += f.blockingDuration;
}
// trade-off: blockingDuration includes rendering time while longtask-derived
// TBT did not, so the new series runs higher. Annotate the dashboard on the
// switch date rather than comparing the two series directly.
Expected outcome: a blocking figure that tracks INP input delay more closely than the old one.
3. Retire the long-task beacon fields
Once a release has shipped with both, remove longtask collection from the beacon. Most teams save 200–600 bytes per beacon on heavy pages.
// Before: { longTasks: [{start, duration, attribution: 'unknown'}, ...] }
// After: { loaf: [{start, duration, blocking, topScript: {src, fn, ms}}] }
// trade-off: if a third-party RUM vendor still computes metrics from longtask
// entries it reads directly from the page, removing YOUR observer does not
// affect it — but check their docs before assuming their charts are unchanged.
Verification
Pick five interactions that RUM marks as slow and reproduce them in the lab with both observers running. For each, confirm the LoAF entry's script list names the code you would have blamed after manual profiling. If LoAF consistently identifies the culprit and Long Tasks either reports nothing or "unknown", the migration is justified. Then confirm beacon payload size dropped and that the INP attribution coverage — the share of INP beacons with a named top script — rose above roughly 70%.
When Long Tasks Still Earn Their Place
Keep a longtask observer only if you have a long-running historical chart you cannot re-baseline, or a Lighthouse-style TBT comparison that requires task semantics. For new instrumentation there is no case for it: LoAF reports a superset of the problem with attribution that actually names code.
Cost and Overhead Compared
Neither API adds meaningful work to the browser itself — both are recorded internally whether or not anyone observes them. The difference is in what your code does with the entries.
A longtask entry is small and flat: a name, a start time, a duration, and a short attribution array. A LoAF entry is larger, and its scripts array can hold a dozen nested objects with long URL and invoker strings. On a busy page that produces 30 long frames during load, naive collection allocates several hundred objects that sit in memory until the beacon is sent. That is negligible on a laptop and measurable on a low-end phone with an aggressive garbage collector.
The practical rule is to filter in the observer callback, not later: discard frames under 50ms, discard frames with no overlap with any interaction once the page is interactive, and keep only the top scripts of the frames you retain. With that discipline the memory footprint of LoAF collection is within a few kilobytes of the old long-task observer while carrying far more useful information.
Throughput is similar. Observer callbacks are batched and delivered asynchronously, so neither API runs code inside the long frame itself. What occasionally surprises teams is that a heavy callback — one that serialises entries to JSON on arrival — becomes a long task of its own and shows up in the next LoAF entry, attributed to your RUM script. If your own beacon code appears in the top-scripts list, move serialisation to the send step.
FAQ
Is Total Blocking Time in Lighthouse affected by LoAF?
No. Lighthouse still computes TBT from the trace's tasks in the lab; LoAF is a field-and-page API. The two are related — both use a 50ms threshold — but a page can have good lab TBT and still produce long animation frames in the field because real devices are slower and real interactions overlap third-party work.
Will other browsers implement LoAF?
At the time of writing only Chromium ships it. That limits it to a diagnostic role: you cannot compute a cross-browser metric from it, but you can find the scripts responsible, and those scripts are usually just as expensive in other engines. Fixes discovered through Chromium attribution typically improve Safari and Firefox users too.
Related
- Finding slow scripts with LoAF script attribution — putting the script list to work.
- Reducing Total Blocking Time in Lighthouse — the lab metric that still uses task semantics.
- Diagnosing presentation delay with LoAF — the rendering tail Long Tasks could never see.