Reading time and consent
Page views need nothing in the browser. Reading time, scroll depth and events such as “added to cart” can only be measured in the browser, so they need a small script, the browser collector. Because a script in the browser that sends data needs the visitor’s consent in the EU, Looku runs it only after your site has asked and the visitor has said yes. Asking is your site’s job.
What you get: for consenting visitors, how long each page was read and how far it was scrolled (content module),
and your own named events. You need: a connected Next.js site and the content
module turned on:
docker compose run --rm ingest site:modules myblog content(Run it in your deploy folder. If the site has other modules, list them all: site:modules myblog content,shop.)
How it works
Section titled “How it works” Visitor's browser ──consent──▶ collector starts │ sends reading time when the visitor leaves the page ▼ Your site: POST /api/looku (same address as your site) │ checks the request, removes IP address and user agent, signs ▼ Looku receiverThe collector sends to your own site, not to Looku, so the visitor’s browser never contacts Looku. Your site’s route forwards the data without the IP address or user agent, and Looku stores only the day, not the time. The collector stores nothing in the browser.
-
Copy the collector
packages/browser/src/index.jsinto your project aslib/looku-browser.js. It has no dependencies. -
Add the route that receives the data,
app/api/looku/route.js:import { createCollectHandler } from "../../../lib/looku.js";export const POST = createCollectHandler();The import goes to the connector you copied in Connect a Next.js site; adjust the number of
../to where yourlibfolder is. The route accepts requests only from your own site’s pages. -
Start the collector after consent. Where your site handles the consent choice, call
start()when the visitor agrees andstop()when they withdraw. A minimal example, if you do not have a consent tool yet. Save it ascomponents/Consent.jsin your project root:"use client";import { useEffect, useState } from "react";import { start, stop } from "../lib/looku-browser.js";export default function Consent() {const [choice, setChoice] = useState(null);useEffect(() => {const saved = localStorage.getItem("consent-reading");setChoice(saved);if (saved === "yes") start();}, []);const decide = (value) => {localStorage.setItem("consent-reading", value);setChoice(value);if (value === "yes") start();else stop();};if (choice !== null) {return <button onClick={() => decide(choice === "yes" ? "no" : "yes")}>Change measurement choice</button>;}return (<p>May we measure how long pages are read?{" "}<button onClick={() => decide("yes")}>Allow</button> <button onClick={() => decide("no")}>Deny</button></p>);}Then in
app/layout.js, addimport Consent from "../components/Consent.js";at the top and<Consent />inside<body>, after{children}. This example remembers the choice in the visitor’s browser; that is your site remembering a choice the visitor made, not Looku storing anything. If you use a consent tool, callstart()andstop()from its callbacks instead. -
Deploy and check. Open your site, allow, read a page for a while, then go to another page or close the tab. In the dashboard, the content view shows the reading time.
Events
Section titled “Events”After start(), send named events:
import { event } from "../lib/looku-browser.js";
event("add_to_cart", { items: [{ sku: "A1", quantity: 1 }] });event("newsletter_signup", { target: "footer" });Names are up to 40 characters: lowercase letters, numbers, _, . and -. target (text), value (a number), items (products) and
props (a small object) are optional. Do not put personal data in them: no names, emails or order numbers that
can be linked to a person. Before start() and after stop(), event() does nothing.
Good to know
Section titled “Good to know”- The collector measures one page at a time and sends when the page is hidden or left, also on client-side navigation in Next.js and other single-page apps.
- Time is counted only while the page is visible: a tab left open in the background does not add reading time.
- Visitors who send Do Not Track or Global Privacy Control are not measured, even if they consent.
- Your privacy notice must describe this; see Privacy and the law.