npm i use-first-interaction

Load it when they arrive.

Analytics, chat widgets and heatmap recorders do not need to be in the first paint. They need to be there by the time someone is actually using the page — which is a moment the browser can tell you about.

delay

Nothing has been fetched.

0.0 s on this page — no interaction yet.

Click, scroll, or press a key. Reading counts: a scroll is an interaction, and that is the point — someone who leaves without one was never going to see the widget.

Two hooks

One reports the interaction as a boolean. The other runs something once, when it happens.

import { useFirstInteraction } from "use-first-interaction";

export default function Analytics() {
  const interacted = useFirstInteraction({ delay: 2500 });

  return interacted ? <Tag /> : null;
}
import { useOnFirstInteraction } from "use-first-interaction";

useOnFirstInteraction(async () => {
  const { hotjar } = await import("react-hotjar");

  hotjar.initialize({ id, sv });
}, { delay: 2500 });

Both are false on the server and on the first client render, so neither changes what hydration compares.

Options

delay

Wait this long after the interaction before reporting it. The interaction itself is the frame the visitor cares most about, and it is the one moment not to spend on a third party.

events

keydown, pointerdown, scroll, touchstart, wheel. Scroll is on the list because a reader who only reads never presses anything.

timeout

Report the interaction anyway after a while. Off by default: a visitor who leaves without touching anything is exactly the one you did not want to load for.

disabled

Report nothing at all — for switching the deferred work off in development, where a recorder session per hot reload helps no one.

What this is not

It is not a consent gate. Deferring a tracker changes when it loads, not whether you were allowed to load it. It is also not lazy rendering: for something that should appear when it scrolls into view, an intersection observer is the right instrument.