Time on Page vs Session Duration: What Each One Measures

Both are stopwatch numbers, and both have a hole in the same place: the last thing a visitor does. Here is how each is computed, what goes missing, and how to read time without fooling yourself.

By Null Agency · Updated October 24, 2026 · Written for site owners who see a time figure in a dashboard and want to know what it counts

TL;DR: one is a page measurement, the other is a visit measurement

Time on page is how long a single page was in front of the reader. Session duration is the span of a whole visit across every page in it. The first can be recorded by the page itself. The second needs a rule for where a visit begins and ends, and that rule is where tools disagree.

What time on page actually measures

Time on page answers a narrow question: for how long was this page open in front of someone? It belongs to one pageview. If a visitor reads three pages, there are three time-on-page values, one per page, and no single number for the visit.

There are two ways to get that value. The old way is to subtract timestamps: when a visitor loads page B, the tool assumes they left page A at that moment, so A's time is the gap between the two pageviews. The newer way is to have the page report its own elapsed time at the moment it is hidden or closed. The first needs a next pageview to exist. The second needs a message to arrive after the visitor has already stopped looking.

Both approaches measure presence, not attention. Neither knows whether the reader was reading, skimming, on a call or making coffee.

Why the last page is the hard one

In the timestamp-gap model, the last page of a visit has no successor, so there is nothing to subtract. It records zero or is left out. A visitor who lands on one article and reads it for six minutes looks identical to one who landed and left after a second. This is the best-known blind spot in classic web analytics, and it is why a one-page visit in a hit-based tool shows no duration at all.

The leave-beacon model fixes this for the common case, because the last page reports its own time. It trades one problem for another: the message is sent while the page is closing, and a tab killed by the browser, a crashed process or a dead network connection can lose it. Some pageviews end with no time value, and a good tool leaves them out of the average rather than counting them as zero.

What session duration actually measures

Session duration is the elapsed time of a visit: from its first interaction to its last. Everything hard about it is in the word "visit". Cookie-based tools typically define a visit with an identifier plus an inactivity timeout, and the duration is the time between the first and last recorded activity inside that window. Change the timeout and the duration changes, with no change in visitor behavior.

Then the last-page problem applies again, now to the whole visit. If duration is last activity minus first activity, the time spent on the final page is never added, because nothing marks its end. A visit made of one long read has a duration of zero. A visit of two quick pageviews followed by a ten-minute read on the last page shows a duration of seconds. Session duration therefore tends to undercount, and it undercounts most on content sites where the visitor's longest stay is the page they leave from.

Averages make this worse. Session duration averages usually fold in the zero-length one-page visits, which pulls the mean down, or exclude them, which makes it a statistic about multi-page visitors only. Two tools can both report "average session duration" and be describing different populations.

What GA4 does differently

GA4 moved away from the timestamp-gap approach. It reports engagement time, built from the time a page or app screen was in the foreground rather than the gap between hits, which lets it count time on a final page. That is a genuine improvement on Universal Analytics, and it is one of the places where GA4 is the better instrument. The cost is the same as always: cookies by default, a consent banner in the EU and UK, and a heavy tag. Check the current Google Analytics documentation for the exact definitions, since Google adjusts its reports; our GA4 alternative guide covers the trade-offs.

How GhostMetrics measures time on page

The mechanics are short enough to state completely.

  1. The clock starts when the tracker runs. On load, gm.js stores the current time. The pageview beacon goes out immediately, and it carries no time value yet.
  2. The clock stops at the first hide or unload. The tracker listens for the tab becoming hidden and for beforeunload. Whichever fires first sends a leave beacon with the elapsed time in whole seconds. A latch ensures one leave beacon per page: later hides on the same page do not send a second value.
  3. The server attaches it to the pageview. The leave beacon is matched to the newest row for the same site, the same path and the same daily session hash within the previous six hours, and the seconds are written to that row. The row is also flagged as an exit, which is how the dashboard knows a time value exists.
  4. Averages use measured rows only. Average time on page, site-wide and per page, is computed over rows that received a leave beacon. Pageviews whose beacon never arrived are skipped, not counted as zero seconds.
  5. Single-page apps get one value per route. A pushState or popstate navigation sends a leave for the route being left, then starts a fresh clock for the new one. A click on an in-page #anchor link also fires popstate in browsers, so on any site it ends the current measurement and starts a new pageview on the same path.

Two consequences are worth stating before you read a number.

The clock stops at the first hide, not the last. A reader who switches tabs after 20 seconds and returns to read for four minutes is recorded as 20 seconds. On phones, switching to another app typically hides the page, so a reader who flips away mid-article and comes back has the same effect. Time on page is closer to "time until the reader first looked away" than to total reading time.

The clock runs while the tab is open and visible, or simply open. The tracker uses wall-clock seconds since it started, with no idle detection. A tab left in the foreground over lunch is recorded as a long visit until it is hidden.

Where the numbers show up

All of these are on the free plan:

Why GhostMetrics has no session duration

It does not report one, and the design explains why. A session in GhostMetrics is one browser tab. The tracker keeps a pageview counter in sessionStorage, cleared when the tab closes, with no cookies, no visitor IDs and no inactivity timeout. A duration would need a start time and an end time for that tab-session, and the product does not store either as a visit-level value. Each stored time belongs to one pageview.

That is a deliberate trade, and it has a cost you should weigh. If you report "average visit length" to a client or a board, GhostMetrics will not supply it. What you can build from the free export is a per-page picture: sum or average time_on_page by page, source or campaign, using only rows where is_exit is 1, as the dashboard does. What you cannot build is a visitor-level timeline, because no identifier links one session to the next.

The same constraint applies to returning visitors, per-visitor funnels and cross-session attribution. If those are requirements, GA4 or another identifier-based tool is the right instrument; our Google Analytics alternatives roundup lays out the field.

QuestionTimestamp-gap toolsGA4GhostMetrics
Unit of timeGap between consecutive pageviewsEngagement time, from foreground timeSeconds from tracker start to first hide or unload
Last page of a visitNot measured; one-page visit reads as zeroMeasured as engagement timeMeasured when the leave beacon arrives
Session duration figureYes, last activity minus firstEngagement-based figures; check current docsNone
Idle or unattended tabCounted until the next hit or timeoutForeground-based; check current docs for edge casesCounted until the tab is first hidden
Missing dataZero or omittedModel-dependentSkipped in averages, never counted as 0 seconds
Identifier neededCookie or similarCookie by defaultNone; tab-scoped counter only

A worked example with made-up numbers

The figures below are illustrative, not benchmarks. Suppose a blog post gets 10 pageviews in a week, and leave beacons arrive for 6 of them. The measured times are 8, 12, 40, 95, 180 and 420 seconds. The other four tabs were closed by the browser or lost their connection before a beacon could send.

The dashboard averages the six measured rows: the sum is 755 seconds, so Avg Time reads 2m 6s. It does not divide by ten. The median of the same six values is 67.5 seconds, a figure the dashboard does not show, and the gap between the two is the point. One 420-second row, which could be a reader or could be an abandoned tab, moves the mean from 67 seconds without it to 126 seconds with it.

The distribution tells the honest story. The dashboard's distribution panel is site-wide, so bucket this page's six rows from the export the same way. Of the six measured pageviews, one is under 10 seconds, one is 10 to 30 seconds, two are 30 seconds to 2 minutes, one is 2 to 5 minutes and one is over 5 minutes. That is a spread, not a typical reader, and it is why this guide keeps pointing at the distribution rather than the average.

One more thing the example shows: the four missing beacons are not zeros, and they are not evenly distributed either. Pageviews that end in a crash or a dropped connection are not a random sample of readers, so treat any average as a description of the visits that could be measured.

How to read time on page honestly: a checklist

  1. Start with the distribution, not the average. Open the Time-on-Page Distribution first; it covers the whole site, not one page. If most pageviews sit in two adjacent buckets, the average means something. If they are spread across all five, it does not.
  2. Group pages by job. A pricing page, a docs page and a 3,000-word guide should not share a target. Compare Avg Time within a group, and against the same page last week.
  3. Pair time with scroll depth. In the All Pages table, long time with deep scroll suggests a finished read. Long time with shallow scroll suggests a tab left open or a stuck reader. Short time with deep scroll suggests a skimmer.
  4. Check the size of the sample. A page with a handful of views can swing its average on one outlier. Prefer pages with enough measured rows that no single value dominates.
  5. Remember what the clock excludes. Time after the first hide is not counted, so pages people switch away from and return to will read low. Pages left open and visible will read high.
  6. Use attention minutes for ranking, not for precision. Attention Minutes is a sum, so a popular page with a modest average can outrank a niche page with a long one.
  7. Use page counts where you wanted session duration. If the question was "do visitors go beyond the first page", bounce rate and the Pages / Session card answer it directly, with no timing assumptions at all.
  8. Count the action. Where time is a proxy for interest, measure the interest itself with a data-gm-event on the button that matters. The custom events guide shows the setup.

Mistakes that distort the story

Treating time as attention

Presence is not attention. A long time on page is compatible with a delighted reader and with a forgotten tab. Use it as a shape, and let scroll depth and counted actions do the work of telling the two apart. Our guide to scroll depth tracking covers the companion signal.

Comparing durations across tools

A timestamp-gap average, a GA4 engagement-time figure and a leave-beacon average describe different things. If you migrate, expect the headline number to jump on day one and write the date next to the chart. Run both tools in parallel for a few weeks and note the offset instead of reading it as a change in your audience.

Chasing a higher average

The cheapest ways to raise average time are to slow the page, bury the answer or add a modal the reader must dismiss. None of these helps anyone. If a change improves the number and worsens the page, the number stopped measuring what you wanted. Real-user load timings belong in the vitals panel; see real-user Core Web Vitals monitoring. GhostMetrics captures FCP, LCP, CLS and TTFB and does not capture INP.

Reading a bounce and a short time as the same thing

Bounce rate counts single-page sessions, and time on page measures duration. A one-page visit can be 4 seconds or 4 minutes, which is why the two metrics are best read together. The full comparison is in bounce rate vs engagement rate explained.

Ignoring traffic that never reads

Spikes of very short visits from one referrer can be a link preview, a scraper or a misconfigured campaign link. Check the Referrers panel for the dates in question before rewriting a page.

Which time metric for which question

QuestionLook atWhy
Do people read this article?Its Avg Time and Avg Scroll in All Pages, plus its rows in the exportThe distribution panel is site-wide, so one page's shape comes from bucketing its exported rows
Where does reader time go across the site?Attention Minutes by pageA sum shows total time, not per-visit time
Do visitors go past the first page?Bounce rate and Pages / SessionA page count needs no timing assumptions
Is a landing page failing fast?Its Avg Time and Bounce Rate in All Pages, plus the click eventVery short stays plus single-page sessions point to a page that does not hold the visitor
How long is a typical visit?GA4 or another identifier-based toolGhostMetrics stores time per pageview, not per visit
Is the page slow to arrive?Its row in the per-page vitals tableTiming problems show up in load metrics before they show up in time on page

The short version: time on page tells you how long a page held someone until they first looked away, and session duration tells you, at best, how long a visit lasted without its final page. Neither says whether the visitor was satisfied. Combine a time distribution with scroll depth and a counted action, and you are as close as page-level analytics gets. For the wider routine, our weekly dashboard guide shows where these panels fit.

Time on page, scroll depth and bounce rate in one dashboard

Cookieless$0 forever1 site free$9.99/mo Pro

GhostMetrics puts average time on page, a time-on-page distribution, attention minutes, scroll depth and session depth side by side, so a long or short stay is one click from the evidence that explains it. It sets no cookies, stores no IP addresses (they are hashed) and keeps no cross-session identifier. Install is one script tag in <head>, about 3 KB compressed on the wire. It has no session duration figure; if you need one, use GA4.

The free plan is free forever, no card: one website, unlimited pageviews, a real-time dashboard, every view and full history. Raw event export as CSV or JSON is on every plan, including free. Pro is $9.99/mo flat with unlimited websites (fair-use ceiling of 250) and a public shareable dashboard, and it starts with a 30-day free trial that needs a card and charges nothing for 30 days.

Start free, no card See the live demo

FAQ

What is the difference between time on page and session duration?
Time on page is how long one page was on screen. Session duration is the span of a whole visit across all its pages. Time on page can be measured from a single page's own load and leave events; session duration needs a rule for where a visit starts and ends, which is why it varies the most between tools.
Why is the last page of a visit often missing from duration?
Older hit-based tools infer time by subtracting one pageview timestamp from the next. The final page has no next pageview, so it contributes nothing, and a one-page visit shows zero. Tools that send a separate event when the tab is hidden or closed can measure the last page, but only if that event actually arrives.
Does GhostMetrics report session duration?
No. The dashboard reports average time on page per page and site-wide, a time-on-page distribution, attention minutes by page, a Pages / Session card, and a Session Depth panel that buckets pageviews by their position in the tab session. It has no session duration figure.
How does GhostMetrics measure time on page?
The tracker records the time it started on the page and, when the tab is first hidden or the page unloads, sends the elapsed seconds in a leave beacon. For single-page apps, a route change sends the previous route's time. The dashboard averages only pageviews whose leave beacon arrived.
Is a longer time on page always better?
No. A long time can mean a reader finished a long article, or that the tab sat open unattended until it was hidden. A short time can mean a bounce, or a visitor who found a one-line answer. Read time next to scroll depth and the page's job, and compare pages of the same type.

Keep reading

Bounce Rate vs Engagement Rate

Two definitions of a visit that did nothing

Read Your Dashboard Weekly

A 15-minute routine for the panels that matter

Single-Page App Analytics

Route changes, leave beacons, and what SPAs do to time on page

GhostMetrics Live Demo

The real dashboard, live data

Disclosure: GhostMetrics is built by Null Agency and we use it on this site, so we are not a neutral party about our own product. GA4's definitions are set by Google and change over time; check the current Google Analytics documentation before quoting exact figures. The numbers in the worked example are illustrative, not benchmarks.