Verify it works
Thirty seconds to prove the tracker is reporting, and what to check when it is not.
Do this before you close the tab. The two most common install faults look exactly like “no traffic yet”, and both take seconds to rule out.
1. Turn on debug and reload
<script defer data-site="1" data-debug src="https://analytics.example.com/mf.js"></script>
data-debug does two things: it logs every payload to the console, and it lifts the
suppression that stops the tracker reporting from localhost, a .local host or a
file:// page. Reload, and the console prints the payload as it goes out:
[micaforge] { s: 1, t: "pageview", u: "http://localhost:3000/", v: "0.1.0" }
If nothing is logged, the script did not load or data-site is missing. Check the
network tab for mf.js.
2. Watch the request
In the network tab, filter for track. A pageview is a POST to
https://your-host/api/track, usually sent through sendBeacon, with no response body
to read.
Two failures show up here and nowhere else:
net::ERR_BLOCKED_BY_CLIENT: a content blocker stopped it. Not a bug; it is what those extensions do. Proxying the script through your own domain is the answer if the loss matters to you.- CORS or a 404:
data-hostpoints somewhere that is not a Micaforge server. It must be the origin, with no path and no trailing slash.
3. Look at real time
Open your site’s dashboard. A live pageview appears within a few seconds; the realtime window is the last five minutes.
curl "https://analytics.example.com/api/stats/realtime?site=1" \
-H "Authorization: Bearer mfg_your_key"
The response carries the live visitor count, the live agent count and the pages
currently open. If the dashboard is empty but the network tab showed a POST that
succeeded, the hit went to a different site id.
Take data-debug back off when you are done. With it on, your own refreshes count.
Two things that will stop a localhost test
data-debug lifts the tracker’s own suppression, but two server-side guards remain, and
both answer 204 either way. An ingest endpoint that told you why it dropped a hit would
also tell an attacker.
The origin has to match the site. Micaforge only accepts a hit whose page origin is the
site’s domain or a subdomain of it. A page on http://localhost:3000 writing into a site
registered as example.com is refused. Add the development host to the site’s extra
domains to allow it:
curl -X PATCH "https://analytics.example.com/api/sites/1" \
-H "content-type: application/json" \
-b "mf_session=..." \
-d '{"settings":{"domains":["localhost"]}}'
That list is additive and the primary domain is always allowed, so it is also how you serve one site from a docs subdomain, a country domain or a staging host.
A headless browser is not a person. If you are testing through Puppeteer, Playwright or
chrome --headless, the hit is classified as a suspected bot and written to the bot table
instead of the events table. That is deliberate: a browser tracker firing with a headless
signature is either a spoof or an automation run, and neither is a reader. Test in a real
browser window, or read the bot table to confirm the hit arrived.
4. Prove the machine half
The browser half tells you nothing about crawlers, so check the other feed separately. Run the shipper against your log without sending anything:
npx --package=@micaforge/sdk-server micaforge-shipper \
--dry-run --no-follow --from-beginning /var/log/nginx/access.log
It parses the file, classifies what it finds and prints the counts. Named agents are what would be shipped. If that number is zero on a site that has been live for a week, either the log format is not being read or the crawlers genuinely have not arrived: the dry run tells you which, because it also counts the lines it could not parse.
What “working” looks like on day one
- Pageviews within seconds, and a session that ends after 30 minutes of inactivity.
- Engagement time and scroll depth arriving when a page is hidden, not while it is open.
- Web Vitals once per document, on the way out.
- Agent fetches only after the server log is being shipped.
- Empty screens where a capability is not built yet, saying so, rather than a zero dressed up as a measurement.