The two approaches are usually described as a trade between accuracy and richness. That is a fair summary, but it hides the part that decides real configurations: the specific list of things each one physically can and cannot observe.
Client-side: measuring from inside the page
A script runs in the browser after the page loads and reports what it sees.
Only it can see:
- engagement time — how long the tab was actually visible and active, as opposed to open
- scroll depth
- rage clicks, dead clicks, form abandonment
- Core Web Vitals from real visitors — LCP, CLS, INP measured in the browser that experienced them
- viewport, colour scheme, precise device capabilities
- client-side route changes in single-page apps
- anything a user did that produced no server request
It cannot see:
- visits where the script was blocked, failed, or never ran
- crawlers of any kind
- requests to cached pages that never reach your application
- anything at all before the page has loaded enough to execute scripts
Server-side: measuring the request
The count happens when the HTML is requested, in your application, at your origin, or at an edge worker in front of it.
Only it can see:
- every request, including ones where scripts never ran
- crawlers, including which AI crawlers read which pages
- the true response status — a 404 or a 500 is a fact the page-side script on a broken page cannot report
- time to first byte from the serving side
- network-level bot signals, at the edge
It cannot see:
- how long anybody looked at anything
- whether the page rendered correctly, or at all
- interaction of any kind
- client-side navigation in an SPA, which produces no request
- the difference between a page that loaded and a page that started to load
That last group is the reason server-side-only analytics feels thin in practice. You know a request happened. You know almost nothing about whether it was any good.
The comparison that actually matters
| Client-side | Server-side | |
|---|---|---|
| Blocked visitors | Missing | Counted |
| Crawlers | Invisible | Visible |
| Engagement time | Yes | No |
| Scroll, rage clicks | Yes | No |
| Core Web Vitals | Yes | No |
| SPA navigation | Yes | Only with help |
| Bot filtering quality | Weak | Strong at the edge |
| Cached pages | Counted | Missed unless configured |
| Setup cost | One tag | Infrastructure work |
Neither column is a subset of the other, which is why "which is better" has no answer and "which are you running" does.
The honest conclusion: run both
The configuration that answers the most questions is both collectors feeding one system, with a shared identifier so a single pageview seen twice is counted once.
That gives you: complete pageview counts, crawler visibility, real bot filtering, and engagement, vitals and interaction data. It also gives you something neither side alone can produce — the difference between the two counts, which is the share of traffic your script alone was missing.
The reason this configuration is rare has nothing to do with it being a bad idea. It is that server-side counting requires code in your own request path, which a hosted analytics vendor cannot put there on your behalf. The architecture is the constraint, not the product roadmap.
Getting deduplication right
If you run both, the one thing that must work is recognising one pageview seen by two collectors as one event.
That requires both sides to derive the same visitor identifier from the same inputs by the same method, and to agree on what a page path is — trailing slashes, query strings, case. Get it wrong in one direction and every number doubles. Get it wrong in the other and the edge count is discarded, which quietly returns you to client-side-only measurement while displaying a badge claiming otherwise.
It is the least glamorous part of the design and the part most worth checking before trusting any number that comes out of it.
Common questions
Is server-side analytics more accurate than client-side?
More complete for counting, less capable for describing. Server-side sees every request including blocked visitors and crawlers, but cannot observe engagement time, scroll depth, interactions or Core Web Vitals, because those happen in a browser it never touches. Whether that counts as more accurate depends entirely on which question you are asking.
Can server-side analytics track single-page app navigation?
Not on its own. A client-side route change produces no request to your server, so there is nothing to count. Either the app explicitly reports navigation to the server, or a client-side script handles it — which in practice means running both collectors, with the script covering in-page navigation and the server covering the initial load.
If I run both, will my pageviews be counted twice?
Only if deduplication is not implemented properly, and then every number doubles. Both collectors have to derive the same visitor identifier from the same inputs using the same method, and normalise paths identically. This is the part of a dual-collector setup most worth verifying before you trust any figure it produces.