Fathom vs. Vercel Analytics: Portability, Priced
Both are cookieless with a one-line install. The difference is what happens when you leave your host, measured against this site's live CSP, event wrapper and Lighthouse run.
Both of these are cookieless, GDPR-friendly analytics with a one-line install, and both will give you page views without a consent banner. The real difference is not the dashboard. It is what happens to your measurement the day you leave your host — and one of them answers that question with undefined.
The short answer
Choose Vercel Web Analytics if you deploy on Vercel and want zero configuration, first-party requests and no extra script host. Choose Fathom if you host anywhere else, run several sites, or need the data to outlive your hosting decision. Fathom is a product you buy; Vercel Analytics is a feature of a platform you already pay for.
The comparison, on the axes that actually diverge
| Fathom Analytics | Vercel Web Analytics | |
|---|---|---|
| Works off your host | Yes — any host, any stack | No — production Vercel deployments only |
| Install | A script tag, or a framework package | npm i @vercel/analytics, then one component |
| Cookies / consent banner | None | None |
| Script origin | Third-party host, or your own via custom-domain routing | Same-origin first-party path in production |
| Custom events | Yes | Yes, with key–value properties |
| Data if you migrate hosts | Stays yours, keeps collecting | Stops at the migration |
| Pricing model | Subscription, by pageview volume | Included allowance on your Vercel plan, then per-event |
| Billing coupling | Independent of hosting | Same invoice as your hosting |
Pricing on both moves often enough that quoting today's numbers would mislead you by the time you read this — check their own pricing pages, and compare on events per month, because a custom-event-heavy site prices very differently from a brochure site on both.
What "one line" means in each case
This site runs Vercel Web Analytics. The entire installation is an import and a component in the root layout:
import { Analytics } from "@vercel/analytics/next";
// …inside <body>, after the app's children:
<Analytics />
That is genuinely all of it. No site ID, no API key, no account linking, because the deployment already knows which project it is. Fathom's install is comparable in size but not in kind: you paste a script tag carrying a site ID, or install its package and configure that ID — which is precisely what makes it portable. The ID belongs to you, so the same snippet works from Netlify, a VPS, or a static bucket.
That difference is the whole comparison in miniature. Zero configuration and portability are the same trade seen from two sides.
The part the docs bury: no extra CSP host in production
If you run a Content-Security-Policy, third-party analytics usually costs you two allowances — a script-src for the script and a connect-src for the beacon. Vercel Web Analytics costs one. This is our live policy, from next.config.ts:
script-src 'self' 'unsafe-inline' https://va.vercel-scripts.com …
connect-src 'self' <supabase-origin>
connect-src stays 'self'. In production the beacon posts to a same-origin path on your own domain, so there is no cross-origin destination to allow. Fathom solves the same problem the other way round, with optional custom-domain routing — you point a subdomain at their collector, which also makes the requests first-party and, as a side effect, harder for blockers to drop. Both approaches work; the Vercel one is free and automatic, the Fathom one is portable and deliberate.
Custom events are where you should actually decide
Page views are a commodity. The question worth answering is whether the tool can tell you which page did the work, and both can — the difference is in the shape of the data.
We wrap Vercel's track in one typed module (src/lib/analytics.ts) so an event name is a union rather than a string:
type ConversionEvent =
| "preview_opened"
| "buy_clicked"
| "waitlist_joined"
| "blog_cta_clicked"
| "wishlist_added";
export function track(event: ConversionEvent, props?: Record<string, string | number>) {
try {
vercelTrack(event, props);
} catch {
// Analytics must never break UX — swallow any client-side error.
}
}
Five events, each carrying properties: blog_cta_clicked sends { post, target }, which is what makes an article attributable to a specific product page rather than to a session. Vercel's query API can then group by eventData/<property> and filter with OData expressions, and vercel metrics does the same from the CLI. Fathom's model is closer to goals-and-conversions than to arbitrary property dimensions; if your analysis is "which of 238 articles sent buyers to which of 111 products", check that the shape you need is expressible before you commit.
Two implementation notes that apply to either tool, and both are visible in that snippet:
- Wrap the vendor call. It is five lines, it gives you a typed event vocabulary so a typo cannot silently create a new event, and it is the only thing that makes swapping the vendor a one-file change. If you are genuinely undecided between these two, write the wrapper first.
- Never let analytics throw. The
catchis not defensive padding. A tracking call sits inside a click handler on a buy button; an exception there costs you the sale it was measuring.
The cost nobody puts in the comparison table
track() no-ops off a Vercel deployment — the injected script only exists there. That is convenient in local development and it is also the exact shape of the lock-in: every custom event you instrument is measurement that exists only while you stay. Two consequences we hit directly:
Local Lighthouse runs score 96 on Best Practices, not 100. The only failing audit is errors-in-console, and it is entirely the analytics beacon 404-ing locally, because /_vercel/insights/script.js is injected at the edge on real deployments only. On the deployed site it reads 100. It is a measurement artifact rather than a defect — but it is one you have to explain every time someone runs Lighthouse against localhost, and portable analytics does not produce it.
There is no local or preview signal. Events are counted on production deployments. You cannot smoke-test an instrumentation change by clicking it in dev; you ship it and watch. That is a real workflow cost on a site where the conversion events are the product feedback.
Neither is a reason to avoid Vercel Analytics. They are the reasons to be honest that "included with your host" is a price, not the absence of one.
Which one we would pick, and when
| Situation | Pick |
|---|---|
| One site, already on Vercel, want numbers today | Vercel Web Analytics |
| Several sites across different hosts | Fathom |
| Hosting decision is under active review | Fathom — do not couple measurement to it |
| Strict CSP and no appetite for a third-party host | Vercel (or Fathom with custom-domain routing) |
| Need property-dimensioned custom events | Vercel, or verify Fathom covers your shape |
| Want analytics on a separate invoice from hosting | Fathom |
We are in the first row and chose accordingly. The wrapper above is the hedge: five lines that mean the second row stops being a rewrite.
Mistakes and how they show up
| Symptom | Cause | Fix |
|---|---|---|
| No data at all after deploying | Web Analytics not enabled for the project | Turn it on in the project's Analytics tab; it is opt-in per project |
| Events fire locally, nothing in the dashboard | Counted on production deployments only | Verify on a production URL, not localhost or a preview |
Console 404 for /_vercel/insights/script.js | Running off-platform | Expected locally; it resolves on a real deployment |
| Script blocked by your own CSP | script-src missing the analytics host | Add https://va.vercel-scripts.com; connect-src needs nothing extra |
| Numbers far below server logs | Ad blockers dropping a third-party script | First-party routing — built in on Vercel, custom-domain on Fathom |
| A misspelled event name creates a new event | Raw string passed to the vendor SDK | Wrap it and type the name as a union |
| A tracking call breaks a button | Vendor SDK threw inside a click handler | Wrap the call in try/catch |
Frequently asked questions
Do either of these need a cookie banner? Both are designed to avoid one: neither sets identifying cookies for analytics by default. Consent law depends on what your whole site does, so a checkout, a chat widget or an ad pixel can still put you in banner territory regardless of your analytics choice.
Can I run both? Yes, and it is a reasonable way to compare them for a month — you will see how much traffic blockers remove. Keep it short: two scripts is two scripts, and reconciling two sets of slightly different numbers forever is its own tax.
What happens to my Vercel Analytics data if I leave Vercel? Collection stops with the deployment. Export what you need through the Web Analytics API before you migrate; there is no portable agent that keeps running elsewhere. This is the single strongest argument for Fathom.
Is Vercel Web Analytics free? There is an included allowance on Vercel plans and per-event pricing beyond it, so "free" holds until it does not. Price it on your expected events per month — a site firing five custom events per session reaches the boundary far sooner than the pageview count suggests.
Where to take this next
Analytics is one of four scripts most sites end up loading, and the CSP interaction above is the one that breaks deploys. Setting a Content-Security-Policy in Next.js is the full policy this excerpt came from, error monitoring in Next.js covers the other half of production visibility, and our Lighthouse pass explains the 96-vs-100 artifact in more detail.
Templates in this post
ASoc Nexus, ASoc Nimbus and ASoc Nova are built the way this post describes — cookieless analytics behind a typed wrapper, a CSP that already allows the beacon, and no consent banner to retrofit.
Browse the full sets: Next.js landing page templates, Tailwind landing page templates.
