Website Traffic Sources: Count Visits, Not Pageviews

- A traffic source is the outside place a visit came from: search, referral, social, a campaign-tagged link, an AI assistant, or direct.
- Count sources by visit, not by pageview. A click from one of your pages to another carries no outside referrer, so pageview counts stuff direct with your own navigation.
- On one site we run, 3,841 of 5,774 pageviews (66.5%) had no outside referrer over 7 days. Counted by visit, the real no-referrer share was 856 of 1,628 (53%).
- Of those 856 visits, 798 (93%) started somewhere other than the homepage. People rarely type a deep address by hand.
- ChatGPT adds utm_source=chatgpt.com to links in its answers, so a tool that reads only the referrer mislabels that traffic as direct.
Count sources by visit. A pageview is the wrong unit for this job, and it will quietly hand most of your traffic to "direct" before you notice.
The six sources you actually have
Search is a click from a results page. Referral is a click from a link on another site. Social is a click from a social platform. Campaign-tagged links are any URL you tagged with utm parameters, and that tag wins over whatever referrer arrives. AI assistants are clicks from an answer in ChatGPT, Claude, Perplexity or similar. Direct is everything left over: a visit where nothing told you where it came from.
| Source | What identifies it | Where it goes wrong |
|---|---|---|
| Search | Referrer from a search engine | Rarely wrong if the referrer survives the trip |
| Referral | Referrer from another site | Sites that strip referrers look like direct |
| Social | Referrer from a social platform | In-app browsers often send nothing |
| Campaign-tagged | utm_source, utm_medium, utm_campaign on the URL | Overrides the referrer; untagged campaigns fall into direct |
| AI assistants | Usually no referrer, sometimes a utm tag | Identical to direct without the tag |
| Direct | No referrer, no tag | Absorbs every visit that lost its referrer |
Why pageviews inflate direct
Internal navigation is the culprit. When someone clicks from your blog post to your pricing page, the browser sends your own domain as the referrer. That is a same-origin request, and most analytics tools treat it as an internal step, not a new source. So the second pageview has no outside referrer attached to it.
If you count sources per pageview, that internal click now counts as a visit with no source. Do it enough times and direct becomes your biggest channel, which is a measurement artifact.
Here is what that looks like on a site we run. Over 7 days it had 5,774 pageviews and 1,628 visits, where a visit is a run of pageviews with gaps of 30 minutes or less. Counted by pageview, 3,841 of those pageviews (66.5%) had no outside referrer. Counted by visit, 856 visits (53%) arrived with no referrer and no campaign tag. Same traffic, same week, and the pageview number overstates the problem by more than 13 points.
Missing referrers are normal, not an edge case
The browser decides what to send, and it sends less than you expect. Per MDN's documentation on Referrer-Policy, strict-origin-when-cross-origin is the default when a site sets no policy at all. Under it, a same-origin request sends the origin, path and query string. A cross-origin request sends the origin only, and only while the protocol stays HTTPS to HTTPS. Going from HTTPS to HTTP sends no Referer header at all. A link marked rel=noreferrer sends nothing.
Then there is everything that is not a browser tab. Email clients, chat apps and native mobile apps often open links with no referrer. Many of those visits reach you looking exactly like someone who typed your address by hand.
Google's own definition of Direct is narrow
Per Google Analytics 4's default channel group documentation, the Direct channel is a source of exactly (direct) combined with a medium of (not set) or (none). Google describes these users as arriving via a saved link or by entering your URL. Unassigned is the value Analytics uses when no other channel rule matches the event data. So the bucket is defined tightly, and then every referrer-less visit gets placed in it anyway. The definition is honest. The population is not.
Thirty days makes the pattern clearer
Same site, 30 days: 6,745 visits. Of those, 3,035 (45%) arrived with no referrer and no tag. 2,802 of them (92%) started on a page other than the homepage, and 233 started on the homepage. The short window and the long window tell the same story. People land deep, which means a link sent them there, and the link did not say who sent it.
The landing page is a clue about the source. It is never a name for it. If a visit arrives with nothing and starts on a comparison page, something pointed at that page. You cannot tell from your server logs what it was.
AI assistants are a traffic source, and they hide well
Over 30 days, 360 visits (5.3%) carried utm_source=chatgpt.com and no referrer. ChatGPT puts that tag on links inside its answers. If your analytics reads the referrer only, every one of those 360 visits gets counted as direct. There is a working method for catching the tagged ones in how to see AI traffic in your analytics. The untagged ones you cannot separate, and sampled answers versus recorded visits explains why asking a chatbot about your own brand produces a guess rather than a number.
Timing will not find the AI visits for you
We tried it. Match a direct visit to an AI assistant's read of the same page in the 10 minutes before, and see whether the rate beats chance. It found 1 to 2% more matches than chance, with no peak in the first 2 minutes. Timing cannot tell you which direct visits came from AI. Anyone selling you that method is selling you noise.
A crawler fetch is not a visitor
Separate your AI crawler rows from your human rows. A crawler is not a person, even when it fetches a page on someone's behalf. Kymo classifies three categories: ai_answer for a live fetch made because one person asked a question, indexing for a crawler building a search index for later answers, and training for a bulk crawl gathering model data.
The distinction matters because the agents are separate. Blocking training does not remove you from that operator's answers, since the indexing and live-fetch agents carry their own tokens. Should you block AI training bots but allow answer bots? walks through which token does what. You can look up any agent in the AI crawler directory, including OAI-SearchBot (OpenAI's search index crawler) and ChatGPT-User (OpenAI's live fetcher).
Publishing an llms.txt file is a cheap bet and nothing more. Adoption is partial, no major operator has publicly committed to honouring it, and publishing the file is not the same as a crawler fetching it. Only your own server sees which of those happened. Kymo's free llms.txt generator and validator will build and check one in a few minutes. For the wider picture, The Small Business Guide to AEO covers what actually moves.
How Kymo counts sources
Kymo counts sources by visit, not by pageview, so internal clicks stop polluting the numbers. It also splits no-referrer visits in two. If the visit started on the homepage, it is "typed or bookmarked". If it started on any other page, it is "link, no referrer", and Kymo shows you the landing page so you have a clue about where it came from.
The visitor identifier is a salted server-side hash that rotates at UTC midnight, so unique visitors means unique per day. No cookies, no localStorage for tracking, no raw IPs stored. The cookieless identity docs spell out exactly how that works.
Pricing is plain: Solo is $9 a month or $90 a year for up to 10,000 events a month. Studio is $29 a month or $290 a year for up to 100,000 events a month. Every feature is on both plans, the only difference is event volume, AI crawler tracking does not count against your limit, and both start with 14 days free, no card required.
Stop guessing. Watch.
Start tracking it and you get one dashboard for human visits and AI crawler activity on your own site: which pages AI engines read, how many people AI answers sent there, and which of those people completed a goal. That is measured data from your own server, which is a different thing from asking a chatbot and reporting the answer as a finding. The free AI visibility check at kymo.in/tools/ai-visibility-checker reads a URL from public signals, so the site does not need Kymo installed, and the report arrives by email.
Start free → 14-day free trial. No card required.