Troubleshooting
Common reasons Muro looks broken, and how to fix each one in under a minute.
If Muro isn't doing what you expect, almost every problem falls into one of the cases below. This page lists each one with a quick diagnosis and the fix.
First, the obvious one: is your code deployed?
If the snippet is sitting in a local commit and not on production yet, your live site doesn't have it. We've absolutely refreshed the dashboard a dozen times in a row before remembering this one. Deploy your changes, give it a minute, then read on.
"I installed the script but no events show up"
The most common one. Walk through these in order.
Open DevTools and look for the request
Open your site in a browser, open DevTools, switch to the Network tab, filter for collect, and reload. You should see:
- A
POSTtohttps://cdn.muroanalytics.com/collect - A
204 No Contentresponse
If you see the request and a 204, Muro is collecting fine. Move on to "I see the request but no data in the dashboard."
If you don't see the request at all:
- View the page source and confirm the
<script>tag is actually there. A common mistake is pasting it into a CMS preview but not publishing. - Confirm
data-project-idhas your real project ID, not the placeholderYOUR_PROJECT_ID. - Check the Console tab for errors. If you see "Refused to load the script," see the CSP section below.
- Check that you're not on an ad blocker. See the ad blocker section.
If you see the request but a 400 response:
The project might be paused, or your trial might have ended without a plan. Open the dashboard and check:
- Settings → Project → Tracking active. If the toggle is off, flip it on.
- Settings → Billing. If your trial ended without choosing a plan, see "Your trial ended and events are being rejected" below.
If you see the request but a 404 response:
The project ID in your snippet doesn't exist or was deleted. Copy the snippet again from Settings → Project → Tracking script.
I see the request but no data in the dashboard
Two things can cause this:
- Time range mismatch. The default is "Past 7 days" but you just installed, so set the time picker to Live and reload your site in another tab. Your visit should appear within 30 seconds.
- Project filter. If you have multiple projects, make sure the project switcher in the top nav is pointed at the right one.
Events take a few seconds to appear
Muro batches events on the server side for efficiency. Once an event leaves your browser, it goes through:
- The ingestion worker, within roughly 10 milliseconds.
- A queue that flushes every 5 seconds, batching up to 100 events at a time.
- A single database write per batch.
That means there's a normal 5 to 15 second delay between a visit happening and the data being visible. Live mode in the dashboard polls every 30 seconds, so worst case you see your event 35 to 45 seconds after the visit.
If you're testing a fresh install and your visit isn't showing up instantly, give it a minute before assuming something is broken.
Pageviews appear twice for every visit
If your visitor count looks roughly twice what it should be, you're double-firing pageviews. The usual suspects:
- Two copies of
muro.jsare loaded. View source and check. Two<script>tags both pointing at Muro will fire on every navigation. - Your framework's router calls
history.pushStateduring hydration. Most don't (Muro deliberately ignoresreplaceStatefor this reason), but a custom router might. - You're manually calling
window.muro.track('pageview', ...)in addition to letting Muro auto-track. Don't do this. Muro handles pageviews automatically.
If you intentionally need to fire a synthetic pageview (for a hash router, say), use a different event name like view, not pageview.
A custom event isn't appearing
If window.muro.track('signup') isn't showing up where you expect, walk through these:
- Open the Network tab and confirm the event fires. You should see a
POSTto/collectwith"event_type": "custom"in the request body. If you don't see it, yourtrack()call isn't running. Add aconsole.log()next to it and reload. - Check the event name. It must be a non-empty string under 64 characters.
- Check the props. Only strings, numbers, and booleans are allowed. Nested objects, arrays,
null, andundefinedall cause the event to be rejected. See Track custom events for the full rules. - Make sure the script loaded first. If you call
track()beforemuro.jsfinishes loading, you'll get a "muro is not defined" error. See that section below.
Ad blockers
A small percentage of visitors run ad blockers that include muro.js in their blocklist. Those visits will not be captured. There's nothing you can do about this and it's normal. Industry-wide, expect 2 to 10% of traffic to be invisible to any analytics tool.
You don't need to "fix" this. Just be aware that your numbers will be slightly lower than your server logs.
Content Security Policy (CSP)
If you have a Content-Security-Policy header on your site, the browser will block muro.js from loading or from sending events unless you allow the Muro domain.
Add these to your CSP:
script-src 'self' https://api.muroanalytics.com;
connect-src 'self' https://cdn.muroanalytics.com;If you also have default-src, the more specific script-src and connect-src directives override it for those resource types.
Common signs you have a CSP issue:
- The Console tab shows "Refused to load the script 'https://api.muroanalytics.com/muro.js' because it violates the following Content Security Policy directive."
- Or: "Refused to connect to 'https://cdn.muroanalytics.com/collect' because it violates..."
Single-page apps and missing pageviews
Muro tracks SPA navigation automatically by listening for history.pushState and popstate. This covers Next.js, Vue Router, React Router, Astro view transitions, and pretty much every other modern framework.
A few cases where this can break:
- You're using
history.replaceStatefor navigation. Muro deliberately ignoresreplaceStateso frameworks like Next.js don't double-count the initial pageview on hydration. If your framework actually usesreplaceStatefor real navigations (unusual), it won't be tracked. The fix is to callwindow.muro.track('pageview')manually after the route change, or usepushStateinstead. - You're using a router that doesn't touch
historyat all. Hash-based routers and custom in-memory routers won't trigger Muro. Callwindow.muro.track('pageview')after each route change.
Localhost doesn't show a country or referrer
When you develop on localhost, requests don't have a referring site and Cloudflare can't resolve a country. Those fields will be blank in the dashboard for those events.
This is expected and harmless. Real visitor data from production will have those fields populated normally.
"muro is not defined" in the console
The window.muro object is added by muro.js once the script loads. If you see this error, the script hasn't loaded yet at the time your code ran.
Two common causes:
- Your code is in
<head>and runs synchronously, beforemuro.jsfinishes loading. Move the code that useswindow.muroto fire after the page is interactive, or defer it. - Your CSP is blocking
muro.js. See above.
If window.muro is genuinely undefined and you're sure the script is loading, defensive code helps:
if (typeof window !== 'undefined' && window.muro) {
window.muro.track('signup');
}Tracking across multiple subdomains
The Muro snippet binds to a project ID, not a domain, so you can drop the same snippet on yoursite.com, app.yoursite.com, and blog.yoursite.com and they'll all feed the same project. No extra configuration needed.
Two things to know about how this counts:
- Visitors are counted per origin. The daily salt that produces the visitor ID lives in
localStorage, andlocalStorageis per-origin. The same person visitingblog.yoursite.comand thenapp.yoursite.comis counted as two unique visitors. - Sessions don't span subdomains. A user switching from
blogtoappstarts a new session.
If you need a single identity across subdomains, that's not currently supported. Most indie hackers don't need it, but it's a known limitation.
Staging and production traffic are mixed together
You're using the same project ID across environments. Two clean fixes:
-
Create a separate Muro project for staging. Two snippets, two project IDs, two dashboards. This is the cleanest option and lets you actually test the install on staging without polluting production data.
-
Only load the snippet in production. In your framework, render the script tag conditionally:
{process.env.NODE_ENV === 'production' && ( <script async src="https://api.muroanalytics.com/muro.js" data-project-id="YOUR_PROJECT_ID" /> )}Faster to set up, but you can't verify the install on staging.
Pick whichever fits how you work.
The tracking script is blocked by a network or firewall
Corporate networks, school networks, and some VPNs block analytics domains wholesale. The script never even loads. There's nothing the snippet can do about this.
If your audience is heavily corporate (B2B SaaS sold into large enterprises), expect a higher invisible-traffic percentage than a B2C site. There's no workaround other than proxying the script through your own domain, which Muro doesn't currently support.
Your trial ended and events are being rejected
When a 30-day trial expires without a plan being chosen, Muro stops accepting new events. You'll see 400 responses to /collect, and Live mode will sit at zero.
Your existing data is preserved. The dashboard still works for browsing what you already collected.
To resume collecting, open Settings → Billing and pick a plan. Events start flowing again immediately after checkout. No need to reinstall the snippet.
Bounce rate is much higher than expected
A "bounce" is a session with exactly one pageview. If your bounce rate is suspiciously high, the most common reasons are:
- Single-page landing pages. If a page has no internal links, every visit is a bounce by definition. That's correct.
- Bot traffic. Crawlers visit one page and leave. They count as bounces.
- Your tracker isn't firing on SPA navigation. See the SPA section above.
Bounce rate is calculated from sessions, not pageviews. A visitor who reads one page for 10 minutes still counts as a bounce.
Numbers look different from another tool
Muro's numbers won't match Google Analytics, Plausible, or your server logs exactly. That's normal across every analytics tool. Different tools count differently:
- Different bot filtering. Each tool has its own bot list.
- Different session timeouts. Muro uses 30 minutes. Some tools use 25, some use an hour.
- Different ad blocker reach. Each tool is on different blocklists.
- Different sampling. Muro doesn't sample, but some tools do at high volumes.
If two analytics tools agree to within 10 to 15%, that's typically considered a clean install. Pursuing exact matches is rarely worth the time.
Still stuck?
If none of these match what you're seeing, write to support@muroanalytics.com with:
- Your project ID
- The URL where the snippet is installed
- A screenshot of the Network tab showing what's happening on the
/collectrequest
We respond within a business day.