How to Check Your Analytics Is Working in 2026

An empty dashboard has a short list of possible causes, and most of them are not a broken install. This checklist works in any analytics tool, with GhostMetrics as the worked example, and takes you from "nothing showing" to a confirmed cause in the order they are most likely.

By Null Agency · Updated October 20, 2026 · Written for developers and site owners who just installed a tracker and see zeros

TL;DR — test in a clean browser, then read the Network tab

The fastest diagnosis is the Network tab, not the dashboard. If the script request returns 200 and a tracking request fires when the page loads, the install is fine and any remaining problem is on the receiving side or in your test browser.

Start with the browser you are testing from

Most "my analytics is broken" reports are really "my test browser is not a normal visitor". Your own machine is the least representative visitor you have: extensions installed, privacy settings tuned, possibly a VPN.

Before you touch any code, rule out the browser. Three conditions silence almost every tracker:

You can read the two privacy signals directly from the browser console. Open DevTools, switch to the Console tab, and run:

navigator.globalPrivacyControl
navigator.doNotTrack

If the first returns true or the second returns "1", that browser is sending a signal a privacy-first tool will respect. Browsers that do not implement a property give you no value there, which means no signal from that property.

The cleanest test is a fresh browser profile or a private window with extensions disabled and both signals off. If the visit shows up there, your install is correct and your everyday browser is the one filtering you. A share of your real visitors filter the same way. For why a cookieless tool still respects these signals, see cookieless tracking explained.

The Network tab checklist

The Network tab shows what the browser actually requested and what came back. It is the ground truth, and it works the same way for every analytics product. Here is the procedure, written for Chrome, Edge and Firefox, whose DevTools differ in layout but not in substance.

  1. Open the page you want to test in a clean profile (see above), then open DevTools with F12 or Cmd+Option+I on macOS and select the Network tab.
  2. Tick "Disable cache" and reload. This forces the browser to fetch the script fresh, so you see the real status code instead of a cached copy. Keep DevTools open during the reload.
  3. Filter for the script. Type gm.js into the filter box. With GhostMetrics you should see one request to ghostmetrics.nullagency.io for that file. For another tool, filter by its script name or domain.
  4. Read the status. A 200 means the script was delivered. A 404 means the URL in the tag is wrong. A red entry with "blocked" or "(blocked:csp)" points at an extension or a CSP. No entry at all means the tag is not in the HTML the server sent for that page.
  5. Look at the tracking request. Clear the filter, or filter on the tool's domain, and look for a request that fires after the script loads. GhostMetrics sends a POST to /api/events on ghostmetrics.nullagency.io for the pageview, and usually a second one with Core Web Vitals within about ten seconds. Check that they return a success status (204).
  6. Switch to another tab, then come back and look again. GhostMetrics also sends a leave request (time on page, scroll depth) the first time the tab is hidden or the page unloads, once per page view. Closing the tab closes DevTools with it, so switching tabs is the way to see it; to catch it on a link click, tick "Preserve log".

That is the whole test. The script plus its tracking requests, all successful, means the browser did its part. If both are there and the dashboard is still empty, skip to the real-time section below.

Confirm the script tag itself

While you are in DevTools, switch to the Elements tab, search for gm.js with Cmd/Ctrl+F, and check three things. The tag should sit inside <head>. It should carry defer, so it downloads in parallel and runs after the document is parsed, without blocking rendering. And the data-site attribute should hold your real site ID, not the placeholder from the docs. The GhostMetrics tag looks like this:

<script defer src="https://ghostmetrics.nullagency.io/gm.js" data-site="YOUR-SITE-ID"></script>

Search the Elements tab, not "View source": the live DOM is what the browser ran, and a framework or CMS that injects or strips tags at runtime makes the two differ. For host-specific places to put the tag, see how to add analytics to a static site.

Symptom to cause: a lookup table

Use this table to jump from what you see to the most likely cause. It lists causes in rough order of how often they turn out to be the answer on a fresh install.

What you seeLikely causeHow to confirm
Script request is 200, no tracking request firesGPC or DNT is on, so GhostMetrics collects nothing; or the tag has no data-site attributeRun the two console checks; retest in a clean profile with both signals off; check the tag in the Elements tab
Script request is red or blocked, no status codeAd blocker, tracker blocker or DNS filterDisable extensions or use a private window; try another network
Blocked with a CSP message in the consoleThe site's Content Security Policy does not allow the script or the requestRead the console error; it names the directive that refused it
No gm.js request at allTag is missing from that page, or is in the wrong templateSearch the Elements tab for the tag on the exact URL you tested
Script is 404Typo in the src URLCompare the tag character by character with the snippet from your dashboard
Requests succeed, dashboard stays emptyWrong site ID, wrong site selected, or a date range that excludes todayCompare data-site with the ID in the dashboard; check the real-time view
Requests succeed from an automated test, nothing storedGhostMetrics answers headless browsers, Lighthouse and known bots with a success status but stores nothingTest from a normal, visible browser window
First page counts, later navigations do notSingle-page app route change not firing a requestClick internal links with the Network tab open; look for a request per route
Works on production, nothing on stagingThe tag is only rendered in production builds, or the staging host is blockedLook for the script request on the staging URL
Your visits are missing, other people's show upYour browser sends a privacy signal or blocks the requestCompare with a clean profile on the same machine

What blocks analytics, and what to do about each

Ad blockers and content blockers

Content blockers decide by filter list, and lists change. Some browsers ship with built-in tracker blocking. A tool that is cookieless and anonymous can still be on a blocklist, because the lists match on domains and script names, not on behavior. That holds for every analytics product, which is why none can promise a count of every visit.

The practical response is to know your blocker rate for your own audience and treat the dashboard as a floor. If your visitors are developers, the share who block trackers is higher than for a general audience, and your real traffic is higher than the dashboard shows, in every analytics tool. To estimate the gap, compare against your host's request logs or a second tool run in parallel for a week; expect differences, since logs count bots and prefetches that a script never sees. The migration guide describes the parallel-run method.

Content Security Policy

If your site sends a Content-Security-Policy header, the browser refuses anything the policy does not list. Two directives matter for a tracker. script-src decides whether the script file may load. The directive that governs outgoing requests from script, connect-src, decides whether the tracker may send its data. A policy that allows the first and not the second produces a confusing result: the script request is 200, and the tracking request is blocked.

Open the Console tab and look for a message that begins with "Refused to load the script" or "Refused to connect", followed by the policy directive and the blocked URL. Add the host named in the message to the matching directive. For GhostMetrics the script host is ghostmetrics.nullagency.io; for the request host, use whatever host you saw in the Network tab in the previous step. An example, trimmed to the relevant parts:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://ghostmetrics.nullagency.io;
  connect-src 'self' https://ghostmetrics.nullagency.io

Treat that as a pattern, not a drop-in policy: keep your existing directives, and use the host your own Network tab shows for the tracking request. If the policy comes from a meta tag, a CDN rule or a framework setting, change it there; browsers apply every policy they receive, so a CDN rule can undo a change made in the app.

Privacy signals: GPC and DNT

This one deserves a repeat because it catches the people who care most about privacy. GhostMetrics honors Global Privacy Control and Do Not Track and collects nothing when either is on. No cookie, no pageview, no beacon payload. Your dashboard will not show your own visits from that browser, and it will not show any visitor who has the same setting on. If you run a hardened browser, use a second one for install tests.

Your totals exclude those visitors by design, so a log-based count will run higher partly for this reason. Web analytics without a consent banner covers the consent side.

Testing single-page apps and route changes

A traditional site loads a new HTML document for every page, so the tracker runs again on every navigation. A single-page app loads the document once and swaps views with JavaScript, updating the address bar through the History API. A tracker that only fires on document load sees the first page and nothing else, and a site with fifty views reports a one-page visit.

GhostMetrics is SPA-aware: a pushState or popstate navigation is recorded as a new pageview, with no per-route code to add. You still need to test it against your own router, because routers differ. The test takes two minutes:

  1. Open the Network tab, clear it, and tick "Preserve log". Load the app's first page.
  2. Click an internal link that your router handles, without reloading. In the Network tab, no new document request should appear, since that is the point of an SPA.
  3. Look for a new tracking request after each click. With GhostMetrics, one route change produces one pageview request, preceded by a leave request for the page you left. Zero means the navigation was not detected; extra pageview requests mean something fires repeatedly, such as a component that mounts twice in development.
  4. Press the browser back button and check again. Back and forward travel through popstate, which is its own case.
  5. Confirm in the real-time view that the pages you clicked through appear with their own paths, not all under the first URL.

Two edge cases need an extra look. If your router changes only the URL fragment (the part after #), do not assume it is covered; run the same test and read the Network tab. GhostMetrics records the path without the fragment, so /#/about and /#/pricing both report as /. And if the app updates the visible view without changing the URL at all, such as a modal or a tab panel, no tracker can know that a "page" changed, so record it as a custom event instead. The custom events guide shows how, and the framework-specific walkthrough is in analytics for Next.js without Google.

The real-time view, and why your own visits may not show

The real-time view is the shortest feedback loop you have: load a page, then check whether the dashboard registers it. GhostMetrics includes a real-time dashboard on the free plan, so you can verify an install without waiting for a day of data to accumulate. Use it in this order:

  1. Confirm in the Network tab that the tracking request succeeded.
  2. Open the dashboard in a separate window, select the correct site if your account has more than one, and open the real-time view.
  3. Load or reload the page you are testing, then refresh the dashboard and look for the visit and the page path.
  4. If nothing appears, widen the period beyond "today" in case a time-zone difference puts the visit in an adjacent day, then check the site selector.

When the Network tab is clean and the dashboard is still empty, the usual cause is a mismatch between the data-site value and the site you are looking at. Copy the site ID from the dashboard again and compare it with the tag in the live DOM. A single wrong character sends the data to an ID that does not belong to your account. This is also the moment to use the raw export: GhostMetrics lets you export every stored row as CSV from the dashboard, or as JSON from the API, on every plan including free. If the export shows rows for your site and the panels do not, you are looking at the wrong period or site; if the export is empty too, nothing has been stored.

Why your own visits may not show

Beyond the privacy signals, three ordinary reasons explain a missing self-visit:

If your own browsing does register, you are padding your numbers on a small site. Browsing with GPC or DNT on in your everyday browser keeps you out of the numbers automatically.

Staging versus production

A staging site is where installs get tested, and it is where the most confusing results come from. There are two opposite failure modes.

Staging has no tag, so the test proves nothing. Many setups only render the analytics snippet when the build environment is "production", on purpose, to keep test traffic out. If that is yours, the Network tab on staging will show no gm.js request, which is correct. Test on production with a clean browser instead.

Staging has the production tag, so test visits pollute real data. The tag carries a site ID, and any page that loads it reports to that site. If staging, preview deployments and local development all load the same ID, every QA click is counted as a visitor. The free plan covers one website, so a separate staging site means Pro, which adds unlimited websites (fair-use ceiling of 250) at $9.99/mo. Most small teams do the simpler thing and render the tag only in production, then verify installs by reading the Network tab, which does not need the dashboard at all.

A habit that avoids both: after every tag change, check the production URL in the Network tab and note the time, so a later dip or spike has a timestamp to compare against. How to measure website traffic covers reading the numbers once they flow.

Verify an install in two minutes with GhostMetrics

Real-time dashboard on freeOne script tag1 site freeRaw export on every plan

GhostMetrics is cookieless web analytics with one script tag in the head, about 3 KB compressed on the wire. It honors Global Privacy Control and Do Not Track, so those visitors are not collected. The free plan is free forever with no card: one site, unlimited pageviews, a real-time dashboard, every dashboard view, full history, so you can check an install the minute you deploy it. We use it on this site.

Need several sites or a public dashboard URL? Pro is $9.99/mo flat with unlimited websites (fair-use ceiling of 250) and a 30-day free trial; a card starts the trial, nothing is charged for 30 days, and you can cancel anytime. Raw CSV or JSON export is on every plan, including free. Custom events work with data-gm-event="signup-click" on any element or window.ghostmetrics.track('name', { section: 'pricing' }) from code.

Start free, no card See the live demo

FAQ

Why does my analytics show zero visits when I test it myself?
The most common cause is your own browser. If Global Privacy Control or Do Not Track is on, GhostMetrics collects nothing, so a test from that browser shows an empty dashboard even though the install is correct. Content blockers and extensions are the next suspect. Test in a clean profile with extensions off and the privacy signals off, then watch the Network tab.
How do I know the GhostMetrics script loaded?
Open DevTools, go to the Network tab, reload, and filter for gm.js. A working install shows the request with status 200, and the script tag sits in the head with defer. If the request is missing, the tag is not on that page. If it is red or blocked, a blocker or a Content Security Policy is stopping it.
Does GhostMetrics count route changes in a single-page app?
Yes. pushState and popstate navigations are recorded as new pageviews, so you do not add code per route. To test it, keep the Network tab open, click through internal links without reloading, and look for a new request each time. If your router changes only the URL fragment, test it rather than assume.
Can I use the same site ID on staging and production?
You can, but staging visits then land in the production dashboard. The free plan covers one site, so a second staging site needs Pro. The simpler fix is to render the script tag only in production builds and verify on staging with the Network tab, which needs no dashboard.
What if the Network tab shows everything correct but the dashboard is empty?
Check the real-time view first, then widen the date range. Confirm the data-site value matches the site ID in your dashboard exactly, and confirm you are viewing the right site if you have several. If requests succeed and the ID is right, export the raw rows from the dashboard to see what was stored.

Keep reading

Add Analytics to a Static Site

Snippets for Astro, Hugo, 11ty, Next.js and more

Track Custom Events

Count clicks and signups without Google Analytics

Landing Page and Campaign Analytics

Check your UTM-tagged links before launch

GhostMetrics Live Demo

The real dashboard, live data

Disclosure: GhostMetrics is built by Null Agency and we use it on this site, so we're not a neutral party about our own product. The checklist applies to any script-based analytics tool; the GhostMetrics behavior described is limited to what the product does today. Browser DevTools layouts and content blocker lists change, so check your browser's current documentation for exact menu names.