Muro

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 POST to https://cdn.muroanalytics.com/collect
  • A 204 No Content response

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:

  1. 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.
  2. Confirm data-project-id has your real project ID, not the placeholder YOUR_PROJECT_ID.
  3. Check the Console tab for errors. If you see "Refused to load the script," see the CSP section below.
  4. 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:

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:

  1. The ingestion worker, within roughly 10 milliseconds.
  2. A queue that flushes every 5 seconds, batching up to 100 events at a time.
  3. 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.js are loaded. View source and check. Two <script> tags both pointing at Muro will fire on every navigation.
  • Your framework's router calls history.pushState during hydration. Most don't (Muro deliberately ignores replaceState for 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:

  1. Open the Network tab and confirm the event fires. You should see a POST to /collect with "event_type": "custom" in the request body. If you don't see it, your track() call isn't running. Add a console.log() next to it and reload.
  2. Check the event name. It must be a non-empty string under 64 characters.
  3. Check the props. Only strings, numbers, and booleans are allowed. Nested objects, arrays, null, and undefined all cause the event to be rejected. See Track custom events for the full rules.
  4. Make sure the script loaded first. If you call track() before muro.js finishes 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:

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.replaceState for navigation. Muro deliberately ignores replaceState so frameworks like Next.js don't double-count the initial pageview on hydration. If your framework actually uses replaceState for real navigations (unusual), it won't be tracked. The fix is to call window.muro.track('pageview') manually after the route change, or use pushState instead.
  • You're using a router that doesn't touch history at all. Hash-based routers and custom in-memory routers won't trigger Muro. Call window.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, before muro.js finishes loading. Move the code that uses window.muro to 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, and localStorage is per-origin. The same person visiting blog.yoursite.com and then app.yoursite.com is counted as two unique visitors.
  • Sessions don't span subdomains. A user switching from blog to app starts 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 /collect request

We respond within a business day.

What's next

On this page