Skip to content

Engaged time

The engaged-time subpath measures how long the user was actively engaged with the page — visibility plus interaction within an idle timeout. Bundle target: under 1 kB gzip.

import { installEngagedTime } from '@syntarie/tracking/engaged-time';
import { send } from '@syntarie/tracking';
const teardown = installEngagedTime(send, { idleTimeoutMs: 30_000 });
OptionTypeDefault
idleTimeoutMsnumber30_000

The accumulator runs while:

  • document.visibilityState === 'visible' (the tab is focused), AND
  • The user has produced any user-input event (mousemove, key, touch, scroll, click) within the last idleTimeoutMs.

It pauses when the tab goes to the background and resumes on visibilitychange → 'visible'.

{
"type": "engaged_time",
"url": "https://example.com/blog/post",
"ts": 1714665600000,
"props": { "seconds": 47 }
}

props.seconds is rounded. Zero-second pageviews are dropped before emit (no event for “user opened the tab and switched away immediately”).

For per-route engaged-time on a single-page app:

import { endEngagedTimeForPageview } from '@syntarie/tracking/engaged-time';
router.beforeEach(() => {
endEngagedTimeForPageview();
});

This emits the current accumulator (if non-zero) and resets it for the next route.

This is not “time on page” — that would be pageHideTs - pageLoadTs, a metric inflated by users who open a tab and walk away. Engaged time is the actual attention budget the user spent on this content.

It is also not a heatmap, scroll velocity, or rage-click detector. Those are candidates for a v1.1+ analysis layer; the v1.0 SDK ships the raw engaged seconds.