Nuxt integration

Nuxt analytics, error tracking and uptime

BugOtter works with Nuxt, server-rendered or generated. Add one entry to app.head in nuxt.config.ts for cookieless analytics and browser errors. @sentry/nuxt, pointed at your BugOtter DSN, catches server errors. Uptime and a status page start when you add the URL.

  • 1 entryin app.head
  • SSR or generateboth work
  • 3 regionsuptime checks

What BugOtter gives a Nuxt app

  • Analytics with no banner

    One entry in nuxt.config.ts, on server pages and generated ones alike.
  • Server errors from Nitro

    @sentry/nuxt with your BugOtter DSN. Nothing added to the browser bundle.
  • Past ad blockers

    Three routeRules forward the script and beacons from your own domain.
  1. Script in app.head

    In nuxt.config.ts. Details
  2. Proxy with routeRules

    Optional, needs a server build. Details
  3. Server errors

    Server config only, your BugOtter DSN. Details
    Terminal: npm install @sentry/nuxt
  4. Upload source maps

    From the folder your preset publishes. Details
  5. Uptime and deploys

    A health route, heartbeats, deploy markers. Details
    Last deploy step: npx bugotter deploy

Analytics: one entry in app.head

  1. Add the script in nuxt.config.ts

    Nuxt renders app.head into every page, on the server and in nuxt generate output. Quoted keys like "data-app" come out as attributes on the tag. No cookies, no banner.

    nuxt.config.ts
    // nuxt.config.ts
    export default defineNuxtConfig({
      app: {
        head: {
          script: [
            { src: "https://bugotter.com/bo.js", defer: true, "data-app": "YOUR_APP_ID" },
          ],
        },
      },
    });
  2. Route changes are page views

    Nuxt's router changes pages with history.pushState, which the script follows. Each new path counts once, with its own time on page, scroll depth and Web Vitals. Keep private pages out with "data-exclude": "/dashboard" in the same object.

  3. Goals from components

    Call window.bo?.track() in an event handler, never at the top of <script setup>: that part also runs on the server, where there's no window. Forms, file downloads and outbound links are counted with no code.

    app/components/UpgradeButton.vue
    <!-- app/components/UpgradeButton.vue -->
    <script setup lang="ts">
    const props = defineProps<{ plan: string }>();
    function upgrade() {
      window.bo?.track("upgrade_click", { plan: props.plan });
    }
    </script>
    
    <template>
      <button @click="upgrade">Upgrade</button>
    </template>

    Using TypeScript? Declare window.bo once, so track() type-checks:

    bo.d.ts
    declare global {
      interface Window {
        bo?: {
          track(name: string, props?: Record<string, string | number | boolean>): void;
          visitorId(): string | null;
        };
      }
    }
    export {};

npx bugotter init --token bo_… writes your keys (BUGOTTER_DSN, BUGOTTER_PROJECT, BUGOTTER_KEY) to .env.local, or to .env if that's the file you have. Nuxt reads .env in development, so keep them there. It prints the tag for you to place in nuxt.config.ts.

BugOtter visitors chart for one site: daily bars, the previous period as a dashed line, and markers for big payments and an error spike
  1. Payments on the chart. A green marker shows the day a big payment came in.
  2. Error spikes, too. A red marker lines up a bad day with the error behind it.
  3. Last period, dashed, so a drop or a launch spike is obvious at a glance.
  4. Bots kept out of every number, with the count one click away.
One app's visitors in the live demo, with payments, error spikes and the last period on the same chart.

Get past ad blockers with routeRules

Nitro, the server inside Nuxt, can forward three paths to BugOtter from your own domain.

20–40%

of visitors are dropped by ad blockers for most analytics

Three route rules bring them back.
nuxt.config.ts
// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    "/_bo/bo.js": { proxy: "https://bugotter.com/bo.js" },
    "/_bo/api/collect": { proxy: "https://bugotter.com/api/collect?via=YOUR_PROXY_KEY" },
    "/_bo/api/errors": { proxy: "https://bugotter.com/api/errors?via=YOUR_PROXY_KEY" },
  },
  app: {
    head: {
      script: [{ src: "/_bo/bo.js", defer: true, "data-app": "YOUR_APP_ID" }],
    },
  },
});
  • One rule per path, as above. A wildcard rule (/_bo/**) can't carry the ?via= key, and the key is what proves visits came through your proxy.
  • Nitro passes on the request body and the browser's headers, which is what BugOtter checks.
  • This needs a server build. On nuxt generate, use your host's rewrites (Netlify, Vercel, Cloudflare).

Check it once: curl -sI https://yourapp.com/_bo/bo.js should answer 200. Your proxy key is on the app's Traffic tab in BugOtter. Keep the ?via= part: it proves visits came through your proxy, and it only lives in server config, never in the page.

Errors: @sentry/nuxt on the server, sent to BugOtter

Browser errors come from the script. For server routes, API handlers and server-side rendering, use Sentry's Nuxt module with your BugOtter DSN. No Sentry account needed.

  1. Install the module

    terminal
    npm install @sentry/nuxt
  2. Add it, without Sentry's source map upload

    nuxt.config.ts
    // nuxt.config.ts
    export default defineNuxtConfig({
      modules: ["@sentry/nuxt/module"],
      sentry: {
        sourcemaps: { disable: true }, // maps go to BugOtter, not Sentry
        telemetry: false,
      },
      sourcemap: { client: "hidden" },
    });
  3. Init it with your DSN, server only

    sentry.server.config.ts
    // sentry.server.config.ts (project root)
    import * as Sentry from "@sentry/nuxt";
    
    Sentry.init({
      dsn: process.env.BUGOTTER_DSN, // process.env: runtime config isn't ready this early
      dataCollection: { userInfo: false, cookies: false, httpBodies: [], databaseQueryData: false },
    });

    The dataCollection line is for SDK v11; on v10 use sendDefaultPii: false. Leave out tracesSampleRate: BugOtter keeps errors, not traces. Don't add sentry.client.config.ts.

Kept

  • Exceptions and captureMessage events, grouped into issues
  • Release, environment, the request path and runtime
  • Navigation and failed-request breadcrumbs
  • Your user id, hashed (never stored raw)

Accepted, then dropped

  • Performance traces and spans
  • Session replays and profiles
  • Sessions, logs and metrics
  • Console breadcrumbs and click text

On the Cloudflare presets, sentry.server.config.ts isn't used, and Sentry's Cloudflare Nitro plugin takes its place (see Sentry's Nuxt deployment notes). Node, Vercel and Netlify presets work as shown.

A BugOtter error page: a ChunkLoadError on /pricing with a plain summary, Fix with agent, events and users, 14-day bars and the source-mapped line
  1. A plain summary written from the evidence: page, browser, release and visits.
  2. Fix with agent hands the whole issue to Claude Code, Cursor or Codex.
  3. Where your code broke, mapped back to your file and line with your source maps.
One error in the live demo: a plain summary, who it hit, and the line in your code.

Source maps from your Nitro output

sourcemap: { client: "hidden" } (in the config above) writes client maps without a link to them in your scripts. Upload them after nuxt build from the folder your preset publishes, so --delete removes them from what goes live:

PresetUpload from
Node server, nuxt generate.output/public
Vercel.vercel/output/static
Netlify, Cloudflare Pagesdist
package.json (Node preset)
"build": "nuxt build && npx bugotter sourcemaps .output/public --delete"

Bundles land under /_nuxt/, as the browser loads them. The CLI needs BUGOTTER_PROJECT and BUGOTTER_KEY (the upload key; keep it in CI, never in runtimeConfig.public). The source maps guide covers releases.

Uptime, a status page and deploys

3 regions

check your app from the moment you add the URL

Add one more check on a server route that reaches your database, and expect ok to be true:

server/api/health.get.ts
// server/api/health.get.ts
export default defineEventHandler(async () => {
  await useDatabase().sql`select 1`; // or your own client: prove the database answers
  return { ok: true };
});

Nitro scheduled tasks or an outside cron can call a BugOtter heartbeat URL when they finish, so a run that's late or fails alerts you.

Deploys: automatic on Vercel once connected. On Netlify, add your app's deploy webhook as a Deploy succeeded notification. Elsewhere, run npx bugotter deploy as the last step.

Status page: at bugotter.com/status/your-app, with a README badge. Pro can use your own domain.

BugOtter uptime for one app: 30 days of daily bars with two amber days, a response time chart, and the latest check from US East, Europe and US West
  1. Every day at a glance. Amber means a short outage that day.
  2. Response time, median and p95, and the slowest region.
  3. Three regions check your URL. An outage only counts when two agree.
Thirty days of uptime in the live demo, with response times and the latest check from each region.

Or ask your coding agent

Connect BugOtter to Claude Code, Cursor or Codex once over MCP. It signs in through your browser, read-only unless you allow changes. Then paste one line.

Claude Code, once
claude mcp add --transport http --scope user bugotter https://bugotter.com/api/mcp

Then ask, in plain words:

  • Add BugOtter to this Nuxt app: the script in app.head, the three routeRules proxy rules, and @sentry/nuxt on the server only with my BugOtter DSN.

  • Using BugOtter, list this app's server errors since the last deploy and fix the newest one.

  • Add a /api/health server route that checks the database, then add it as an uptime check in BugOtter.

Cursor, VS Code, Codex and the rest have their own one-step setup at Docs: your agent (MCP). Every setup screen in BugOtter also has a Copy prompt for your agent button with your keys filled in.

Questions

Does BugOtter count Nuxt page changes?

Yes. Nuxt uses vue-router's web history, which changes pages with history.pushState. The script listens for that and for the back button, so each new path is one page view. router.replace() swaps the entry instead, so it isn't counted as a new page view.

Why app.head in nuxt.config and not useHead in app.vue?

Both render the tag into the HTML. app.head in nuxt.config is set once for the whole app and ends up in pages made by nuxt generate too, so it's the simplest place. If you prefer useHead, call it in app.vue, so the tag is on every page.

Can I use the routeRules proxy with nuxt generate?

No. Route rule proxies run in Nitro, the Nuxt server, and a fully static site has none. Use your host's rewrites instead: Netlify, Vercel, or a Cloudflare Pages Function. A server build on any Nitro preset can use routeRules.

Do I still need node --import for @sentry/nuxt?

Not on SDK v11: the module bundles sentry.server.config.ts into the server build and starts it on boot, which is why it works on Vercel and Netlify too. On v10 and older, start the server with node --import ./.output/server/sentry.server.config.mjs .output/server/index.mjs.

Should I add sentry.client.config.ts?

No. The script already catches browser errors and failed promises, with the pages and failed requests before each one. Without a client config file, the module adds nothing to the browser bundle.

Where is the status page?

At bugotter.com/status/your-app once you turn it on, with uptime and incidents only. Pro can serve it from your own domain.

Sources

Checked Nov 4, 2026 against the Nuxt config reference (app.head, sourcemap), Nitro route rules, and Sentry's Nuxt install guide. The sourcemaps.disable option is from the @sentry/nuxt 11.10 module source. BugOtter isn't affiliated with Sentry or Nuxt.

Watch every app you ship, from one calm list.

14 days of everything, no card needed. Plans from $10 a month, billed yearly.