Muro
Advanced

Keep staging and localhost out of your data

Stop visits from localhost, preview deployments and staging sites from landing in your production numbers.

Muro counts every page that loads your snippet with your project ID. It doesn't filter out localhost, preview URLs or staging domains, and there's no setting to exclude your own visits. If your snippet is in your codebase, every npm run dev session and preview deployment shows up in your dashboard and can set off insights.

You have two options: send non-production traffic to a separate project, or only render the script in production.

Option 1: use a separate project for staging

Create a second Muro project for staging (project switcher → New project) and use its project ID outside production. Websites are unlimited on every plan, so this costs nothing extra.

This is the best choice when you want to test tracking and custom events on staging before you ship. Pick the project ID from an environment variable your build already knows about, for example:

// app/layout.tsx (Next.js)
const muroProjectId =
  process.env.VERCEL_ENV === 'production'
    ? 'YOUR_PRODUCTION_PROJECT_ID'
    : 'YOUR_STAGING_PROJECT_ID';

Then use muroProjectId as the data-project-id value.

Option 2: only load the script in production

Render the script tag only when the site runs in production.

Your root layout renders on the server, so you can check environment variables there. NODE_ENV is production for next build and next start, and development for next dev:

// app/layout.tsx
const isProduction = process.env.NODE_ENV === 'production';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        {isProduction && (
          <script
            async
            src="https://api.muroanalytics.com/muro.js"
            data-project-id="YOUR_PROJECT_ID"
          />
        )}
      </head>
      <body>{children}</body>
    </html>
  );
}

On Vercel, preview deployments are production builds too. To leave them out as well, check process.env.VERCEL_ENV === 'production' instead. It's only production on your production deployment.

The same check works in pages/_document.tsx for the Pages Router.

import.meta.env.PROD is true in vite build output and false on the dev server. Add the script from your entry file only in production builds:

// src/main.ts (or main.tsx)
if (import.meta.env.PROD) {
  const script = document.createElement('script');
  script.async = true;
  script.src = 'https://api.muroanalytics.com/muro.js';
  script.dataset.projectId = 'YOUR_PROJECT_ID';
  document.head.appendChild(script);
}

This loads the script a little later than a tag in index.html, after your app bundle runs. To keep it in <head>, add it with a small plugin that only applies to builds instead:

// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    {
      name: 'muro-analytics',
      apply: 'build',
      transformIndexHtml() {
        return [
          {
            tag: 'script',
            attrs: {
              async: true,
              src: 'https://api.muroanalytics.com/muro.js',
              'data-project-id': 'YOUR_PROJECT_ID',
            },
            injectTo: 'head',
          },
        ];
      },
    },
  ],
});

Remove the snippet from index.html if you use either option. Note that vite preview serves the production build, so it's tracked.

If you can't use build-time variables, check the hostname in the browser and only inject the script on your production domain. Replace the hostnames with your own:

<script>
  (function () {
    var hosts = ['example.com', 'www.example.com'];
    if (hosts.indexOf(location.hostname) === -1) return;
    var s = document.createElement('script');
    s.async = true;
    s.src = 'https://api.muroanalytics.com/muro.js';
    s.setAttribute('data-project-id', 'YOUR_PROJECT_ID');
    document.head.appendChild(s);
  })();
</script>

Put this in <head> in place of the normal snippet. It works on any platform that accepts custom head code, and skips localhost, staging domains and builder preview domains such as webflow.io.

If your site has a Content Security Policy, this inline script needs a nonce or hash, or 'unsafe-inline', in script-src, on top of the two Muro domains.

What about my own visits?

Muro has no "exclude my visits" setting. Your visits on your production site are counted like anyone else's. On a busy site they're a rounding error. On a brand-new site, keep in mind that some of the first visits are you.

Verify it works

  1. Run your site locally or open a preview deployment.
  2. Open DevTools, go to the Network tab, filter by collect and reload. There should be no request to cdn.muroanalytics.com/collect.
  3. Open your production site and do the same. You should see a POST to cdn.muroanalytics.com/collect with status 202. Or set the dashboard time picker to Live.

Next steps

On this page