[invisit](https://invisit.dev) · [Docs](https://invisit.dev/docs) · [llms.txt](https://invisit.dev/llms.txt) · [llms-full.txt](https://invisit.dev/llms-full.txt)

# Bounce rate vs engagement rate: how GA4, Plausible and 7 others count

> How GA4, Plausible, Fathom, Umami, Matomo, PostHog and three more define bounce rate, engaged sessions and time on page, with worked examples.

- Author: Miraç Berk
- Published: 2026-11-03
- Page: https://invisit.dev/blog/bounce-rate
- Tags: bounce-rate, engagement, google-analytics, plausible

![A soft 3D ball bouncing off an open browser window beside an hourglass and a page scroll bar, in violet dithered light and shadows.](https://invisit.dev/_astro/cover.CCAIIVSE.png)

In Google Analytics 4, bounce rate is the share of sessions that were not engaged, and engagement rate is the rest: a session is engaged when it lasts longer than 10 seconds, has a key event or has two or more page views. Universal Analytics, Plausible, Fathom, Umami and Matomo use an older rule, where a one-page visit with no event is a bounce however long it lasted, so a three-minute read is a bounce in one tool and engaged in the next.

This post sets the definitions of nine tools side by side, as each vendor documents them in November 2026, scores three sample visits with each, and shows what to measure instead. One of the nine is invisit, the analytics tool we build; its rules come from its source code, which we ran on the sample visits. Why tools disagree on the number of visitors and visits in the first place is a separate question, covered in [why analytics tools show different numbers](/blog/analytics-numbers-differ).

## Bounce rate vs engagement rate: what is the difference?

Bounce rate is the share of visits that did nothing your tool counts as engagement, and engagement rate is the share that did. In GA4 the two always add up to 100%, because Google defines bounce rate as "the percentage of sessions that were not engaged" ([Google](https://support.google.com/analytics/answer/12195621)).

What differs between tools is what counts as engagement. They fall into two families:

- Page-count rules: a visit with one page view is a bounce unless, in most of them, an event happens on that page. Universal Analytics, Plausible, Fathom, Umami and Matomo work this way, and time on the page never rescues a visit.
- Engagement rules: time, interaction or a second page each rescue the visit. GA4, PostHog and invisit work this way, each with its own clock.

Simple Analytics sits outside both, because it reports no bounce rate at all. A bounce rate is only comparable with another bounce rate from the same tool, under the same rule.

## How does GA4 define bounce rate and an engaged session?

GA4 counts a session as engaged when it "lasts longer than 10 seconds", "has a key event" or "has 2 or more screen or page views". Bounce rate is the share of sessions that meet none of the three, and engagement rate is engaged sessions divided by all sessions ([engagement rate and bounce rate](https://support.google.com/analytics/answer/12195621)). The automatic `first_visit`, `first_open` and `session_start` events never make a session engaged, even when marked as key events.

Three details move the GA4 number:

- The 10 seconds can be changed per web stream: Admin, Data streams, your stream, Configure tag settings, Show all, Adjust session timeout, then "Adjust timer for engaged sessions". Google says the default "can be adjusted here" ([tag settings](https://support.google.com/analytics/answer/12131703)) but its help pages do not list the allowed values.
- GA4's engagement time only runs while the page is in focus. The browser sends it in an `engagement_time_msec` parameter when the visitor switches away, navigates or closes the tab ([user engagement](https://support.google.com/analytics/answer/11109416)).
- Bounce rate and engagement rate are missing from most standard reports. An Editor or Administrator has to add them with Customize report.

> [!WARNING]
> A GA4 bounce rate and a Universal Analytics bounce rate measure different things. A chart that runs across your move from UA to GA4 shows a drop in bounce rate that no visitor caused.

## How did Universal Analytics calculate bounce rate?

In Universal Analytics, a bounce was a session that sent "only a single request to the Analytics server", and bounce rate was single-page sessions divided by all sessions ([UA bounce rate](https://support.google.com/analytics/answer/1009409)). Any event ended the bounce unless it was sent with the non-interaction flag.

UA timed a session from its first hit to the first hit on the last page, or to the last engagement hit on that page if there was one ([UA session duration](https://support.google.com/analytics/answer/1006253)). A bounce therefore lasted 0 seconds, and every bounce pulled average session duration toward zero. Time on page was "the difference between the initiation of successive pageviews" ([UA sessions](https://support.google.com/analytics/answer/2731565)), so the last page of a visit got none. Standard UA properties stopped processing data on July 1, 2023, and the interface and API closed on July 1, 2024 ([Google](https://support.google.com/analytics/answer/11583528)). Its page-count rule lives on in Plausible, Fathom, Umami and Matomo.

## Bounce rate, visit duration and time on page in nine tools

As of November 2026, from each vendor's documentation, or its source code where the docs are silent or out of date:

| Tool                | A visit is a bounce when                                              | Time per visit                                                             | Time per page                                                     |
| ------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| GA4                 | 10 seconds or less (adjustable), no key event, under 2 page views     | Average session duration, plus engagement time counted only while in focus | Engagement time, in focus only                                    |
| Universal Analytics | One request: one page, no interaction event                           | First hit to the last page's first or last engagement hit; a bounce is 0 s | Gap between successive page views                                 |
| Plausible           | One page and no custom event; `interactive: false` events don't count | Visit duration; one-page visits count as 0 s                               | From engagement events; inactive tabs not counted                 |
| Fathom              | Exactly one page view in the visitor's 24-hour window                 | Time on site from departure pings; hidden tabs not counted                 | From the same pings                                               |
| Umami               | One page view and no custom event (v3.4.1 source)                     | First to last page view of the visit; one-page visits 0 s                  | None in its metric definitions                                    |
| Matomo              | One page view, no event, download or goal conversion                  | One-page visits 0 s unless the heartbeat timer is on                       | Time on page; the last page needs the heartbeat timer to be timed |
| Simple Analytics    | No bounce rate                                                        | None                                                                       | Median visible seconds; views under 5 s left out                  |
| PostHog             | One page view, no autocapture, under 10 seconds (adjustable)          | Last event minus first event of the session                                | Through the previous page view duration property, in insights     |
| invisit             | Under 10 s of active time, no custom event, under 2 page views        | First to last event of the visit; average active time in the API           | No report; scroll depth per page instead                          |

### How does Plausible calculate bounce rate?

Plausible marks a visit as a bounce unless the visitor "views more than one page" or "triggers a custom event" ([metrics definitions](https://plausible.io/docs/metrics-definitions)). An event sent as `plausible('Custom Event', {interactive: false})` is still recorded but leaves the bounce in place. Plausible's visit duration counts one-page visits as 0 seconds, while its time on page comes from an `engagement` event sent whenever the visitor navigates or switches tabs, so a bounced reader still gets a time on page.

### How does Fathom calculate bounce rate?

Fathom's bounce is "a visitor who views exactly one page during their visit", judged within a 24-hour window: someone who reads one page in the morning and another in the afternoon is not a bounce ([how Fathom measures](https://usefathom.com/docs/start/measurement)). Since its [March 2026 rebuild](https://usefathom.com/changelog/mar2026-analytics-rebuild), Fathom's script sends a departure ping when a page is left or hidden, so a one-page visit gets a real time on site. Hidden-tab time is not counted, a ping reports at most 30 minutes, and a ping never undoes a bounce ([data journey](https://usefathom.com/data)).

### How does Umami calculate bounce rate?

Umami's docs define a bounce as a visit with "only 1 event" ([metric definitions](https://docs.umami.is/docs/metric-definitions)). The query in Umami v3.4.1, released October 23, 2026, counts a visit with one page view and any custom event as not a bounce ([source](https://github.com/umami-software/umami/blob/v3.4.1/src/queries/sql/getWebsiteStats.ts)). Umami's visit duration runs from the visit's first page view to its last, leaving out custom events, so a one-page visit adds 0 seconds.

### How does Matomo calculate bounce rate?

Matomo counts a bounce when a visitor views one page and "triggers no other tracked interaction (such as an event, download, or goal conversion)", and the rule cannot be changed ([Matomo FAQ](https://matomo.org/faq/reports/can-i-change-the-bounce-rate-setting-in-matomo/)). One-page visits last 0 seconds unless you turn on the heartbeat timer with `_paq.push(['enableHeartBeatTimer'])`. It sends a ping when the visitor leaves or hides the page, once at least 15 seconds have passed since the previous request by default ([JavaScript guide](https://developer.matomo.org/guides/tracking-javascript-guide)). The ping updates the duration but creates no action, so a reader with the timer on is still a bounce, now with a real duration.

### Does Simple Analytics show bounce rate?

Simple Analytics reports no bounce rate, and its [Stats API](https://docs.simpleanalytics.com/api/stats) has no bounce field. Its time on page is the median of the seconds a page was visible, from script version 12, and page views under 5 seconds are left out of it as bounced views ([time on page](https://docs.simpleanalytics.com/explained/time-on-page)). It also records how far each page view scrolled.

### How does PostHog calculate bounce rate?

PostHog's bounce is a session with "only one pageview, no autocaptures", that "spent less than 10 seconds on the page", and the 10 seconds can be changed in web analytics settings ([dashboard docs](https://posthog.com/docs/web-analytics/dashboard)). Session duration is the last event's timestamp minus the first's ([source](https://github.com/PostHog/posthog/blob/548ed9202d47856b6ba115486c4478e6cbd6fbaa/posthog/hogql/database/schema/sessions_v3.py)), so the `$pageleave` event the docs ask you to capture is what gives a one-page session its length; without it or another later event, the session lasts 0 seconds. Events you send with `posthog.capture()` are not part of the rule; only autocaptured interactions end a bounce. Time on page comes from the previous page view's duration property, in insights ([session metrics](https://posthog.com/tutorials/session-metrics)).

### How does invisit calculate bounce rate?

invisit counts a visit as engaged when it has at least 10 seconds of active time, any custom event or conversion, or 2 or more page views. Every other visit is a bounce.

Active time is measured in the browser. The tracker counts time while the tab is visible and less than 5 seconds have passed since the last pointer press, key press, scroll, touch or mouse wheel movement, and reports each page's total when the visitor leaves it or moves to another route in a single-page app. Moving the mouse alone does not count.

- Visit duration runs from a visit's first event to its last, and the active-time report sent on leaving is one of those events, so a one-page visit gets its real length.
- Outbound link clicks and file downloads are recorded as custom events, so a visitor who leaves through a link is not a bounce. No event can be marked non-interactive, and the 10 seconds and 2 page views are the same for every site.
- The dashboard overview shows bounce rate and average visit duration, but not while filters are on. In the [REST API](https://invisit.dev/docs/api/rest.md), `GET /v1/sites/{site_id}/analytics/sessions` adds engaged and bounced visit counts and average active time, and the pages report gives each page's bounce rate (bounces among visits that entered there) and its scroll depth. There is no time-on-page report.
- The Engagement switch under Settings, Collection, Tracking features turns active time off. A one-page visit can then only be engaged through an event.

## How one visit is scored by each definition

The same visit gets a different verdict in each tool. Three sample visits:

1. The reader: arrives on a blog post from search, reads for 3 minutes, scrolling every 8 seconds or so, and closes the tab. One page view, no events.
2. The quick click: lands on the home page, clicks Pricing after 3 seconds and leaves 4 seconds later. Two page views in 7 seconds.
3. The app user: opens a one-address tool, such as a calculator or a single-page app screen whose URL never changes, and clicks and types in it for 2 minutes. One page view, no events sent.

| Tool                | The reader (1 page, 3 min)                 | The quick click (2 pages, 7 s)       | The app user (1 URL, 2 min)               |
| ------------------- | ------------------------------------------ | ------------------------------------ | ----------------------------------------- |
| GA4                 | Engaged                                    | Engaged                              | Engaged                                   |
| Universal Analytics | Bounce, 0 s                                | Not a bounce, 3 s                    | Bounce, 0 s                               |
| Plausible           | Bounce, 0 s visit, about 3 min on the page | Not a bounce                         | Bounce, unless you send a custom event    |
| Fathom              | Bounce, about 3 min time on site           | Not a bounce, about 7 s              | Bounce, about 2 min time on site          |
| Umami               | Bounce, 0 s                                | Not a bounce, 3 s                    | Bounce, 0 s                               |
| Matomo              | Bounce, 0 s (about 3 min with heartbeat)   | Not a bounce, 3 s                    | Bounce, 0 s (about 2 min with heartbeat)  |
| Simple Analytics    | No bounce rate, 3 min in time on page      | No bounce rate, both pages under 5 s | No bounce rate, 2 min in time on page     |
| PostHog             | Not a bounce, with `$pageleave` captured   | Not a bounce                         | Not a bounce, through autocaptured clicks |
| invisit             | Engaged, 3 min visit, 110 s active         | Engaged, 7 s visit                   | Engaged, 2 min visit, 118 s active        |

The quick click passes in every tool that reports a bounce rate, while the reader, who stayed more than 25 times as long, is a bounce in five of those eight. That is the case against reading a page-count bounce rate on a blog or a docs site.

For the other eight tools the cells apply each vendor's documented rule; we did not run their scripts. For invisit, we fed the three visits to the tracker's engagement code and to the sessionizer our backend runs, with a simulated clock, and the invisit row is what they returned. Then we checked the clock in a real browser: in headless Chrome 155, 26 seconds on a long page with a wheel scroll every 3 seconds reported 23 seconds of active time and a scroll depth of 100%.

Two more visits split the engagement-based tools:

- The still reader: the same 3 minutes on a short post that fits the screen, without a single scroll or click. invisit records 0 seconds of active time and a bounce, with a 3-minute visit duration, while GA4 counts the session as engaged and PostHog as not a bounce, because it lasted longer than 10 seconds. To invisit's tracker, a visible tab with no input looks the same as one somebody walked away from.
- The skim: one scroll, gone after 6 seconds. That is a bounce in all eight tools.

The app user changes once its screens have their own addresses and your tool records client-side navigation as page views: every page-count rule then sees several pages and stops calling the visit a bounce. Whether yours does is covered in [how single-page apps count page views](/blog/spa-page-views).

## What is a good bounce rate?

There is no single good bounce rate, because the number depends on the definition your tool uses, on what the page is for and on where its visitors came from. The reader above is a bounce in Plausible and engaged in GA4, so a blog full of readers shows a high bounce rate in one tool and a low one in the other.

The vendors' own guidance says as much:

- Google's Universal Analytics help answered "Is a high bounce rate a bad thing?" with "It depends", adding that on "a single-page site like a blog" a high bounce rate "is perfectly normal" ([UA bounce rate](https://support.google.com/analytics/answer/1009409)).
- Plausible's docs give typical ranges: "Below 40% is excellent. 40-60% is common for most sites. Above 70% may suggest visitors aren't finding what they expected", and add that content-heavy sites "naturally trend higher" ([metrics definitions](https://plausible.io/docs/metrics-definitions)). They do not say what data the ranges come from, and they only fit Plausible's page-count rule.
- Fathom's guide declines to publish industry numbers ("I won't give you a fake industry benchmark table"), says a single-job page such as a help article or contact page at 70% or 80% "can be perfectly fine", and treats a whole site under 20% that is not a web app as a reason to check the tracking ([Fathom](https://usefathom.com/learn/what-is-bounce-rate)).

The only peer benchmark from a primary source we found is inside GA4. Its Benchmarking feature shows the median and the 25th to 75th percentile range of a peer group in your industry for metrics including bounce rate and engagement rate, with data from May 30, 2024 ([benchmarking](https://support.google.com/analytics/answer/13771577)). It needs the "Modeling contributions & business insights" setting turned on ([setup](https://support.google.com/analytics/answer/16388466)), it compares you only with other GA4 properties under GA4's definition, and Google publishes no figures outside the product.

So compare a page with itself over time, one traffic source with another on the same page, and the weeks before an edit with the weeks after. Two readings deserve a look at the tracking before any conclusion. A site-wide rate close to 0% usually means a second page view or an event fires on load. A sudden 100% bounce from one city is usually automated traffic, as explained in [our post on bot traffic](/blog/bot-traffic).

## How to measure real engagement instead of bounce rate

Measure what the visitor did on the page: how long they were there and active, how far down they got, and whether they did the one thing the page exists for. All three are a better signal than a bounce rate, and most tools offer them:

| Measure                | What it tells you                        | Where to read it                                                                                                                                                                                    |
| ---------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Active or focused time | Whether the visitor stayed with the page | GA4 average engagement time, Plausible and Fathom time on page, Simple Analytics median time on page, invisit active time in the API                                                                |
| Scroll depth           | How much of the page they reached        | GA4 `scroll` event at 90% depth, Plausible scroll depth, PostHog scroll depth per path, Simple Analytics scrolled percentage, invisit average depth and share of views reaching 25, 50, 75 and 100% |
| A meaningful event     | Whether the page did its job             | The events or goals report of any tool, for a signup, a download or a finished article                                                                                                              |

GA4's [scroll event](https://support.google.com/analytics/answer/9216061) fires the first time "a 90% vertical depth becomes visible". invisit records the deepest point each page view reached, in 5% steps, and reports it per page in the API and on [heatmaps](https://invisit.dev/docs/guides/heatmaps.md#what-is-recorded).

### Which events end a bounce?

Before you add an event, decide whether it should change your bounce rate, because each tool treats a custom event differently:

| Tool                | Does a custom event end a bounce?                                                  |
| ------------------- | ---------------------------------------------------------------------------------- |
| GA4                 | Only if it is marked as a key event                                                |
| Universal Analytics | Yes, unless sent as a non-interaction event                                        |
| Plausible           | Yes, unless sent with `interactive: false`                                         |
| Fathom              | No, a bounce counts page views only                                                |
| Umami               | Yes, in v3.4.1                                                                     |
| Matomo              | Yes: events, downloads and goal conversions                                        |
| PostHog             | No, only autocaptured interactions                                                 |
| invisit             | Yes: every custom event and conversion, automatic outbound and download events too |

### Track a finished article

A read-to-the-end event is the meaningful event for a blog or docs page. This one fires once per page view, when the end of the article scrolls into view, and records how many seconds after load that happened:

```ts title="src/read-to-end.ts"
import { track } from '@invisitdev/web'

// Sends article_read once per page view, when the end of the article scrolls into view.
export function trackReadToEnd(article: Element): void {
  const end = document.createElement('div')
  article.append(end)
  const loadedAt = performance.now()
  const observer = new IntersectionObserver((entries) => {
    if (!entries.some((entry) => entry.isIntersecting)) return
    observer.disconnect()
    const seconds = Math.round((performance.now() - loadedAt) / 1000)
    track('article_read', { seconds })
  })
  observer.observe(end)
}
```

Pass it your article element on each page load, and again after each route change in a single-page app. We ran it in headless Chrome 155 against a local invisit collector: on a page 3,600 pixels tall, `article_read` fired once, 12 seconds in, with `seconds: 12`, as the end came into view. The `seconds` property lets you separate readers from visitors who jumped straight to the bottom. Details on events are in [custom events](https://invisit.dev/docs/guides/events.md#custom-events).

In GA4 the same event is `gtag('event', 'article_read', { seconds })`, and it only makes sessions engaged once you mark it as a key event. In Plausible, `plausible('Article read', { props: { seconds } })` ends the bounce unless you add `interactive: false`. Google's GA4 guide advises against generic keep-alive pings that hold a session open, because inflating session metrics "distorts user behavior data" and makes sessions look engaged; it suggests user-initiated progress events instead ([GA4 sessions guide](https://developers.google.com/analytics/devguides/collection/ga4/sessions)).

## Checklist before you report a bounce rate

- [ ] Write down which rule your tool uses, and its timer if it has one.
- [ ] Never put a Universal Analytics bounce rate and a GA4 bounce rate on one chart.
- [ ] Compare a page with itself over time, or one source with another on the same page, never with an industry number.
- [ ] Decide which events may end a bounce, and send the rest as non-interactive where your tool allows it.
- [ ] Check that client-side navigation is recorded before you read a single-page app's bounce rate.
- [ ] Track one meaningful event per page type, and read it next to scroll depth and active time.
- [ ] Treat a site-wide rate near 0% as a tracking bug until you find the event or page view that fires on load.
