Antlytics logoAntlytics
← Blog
5

Analytics for Nuxt: Setup Guide

How to add privacy-friendly analytics to a Nuxt project. Nuxt's plugin system makes analytics setup clean.

Analytics for Nuxt: Setup Guide

Nuxt is the Vue.js meta-framework, analogous to Next.js for React. Adding analytics to Nuxt requires handling both initial page loads and client-side navigation — the same challenge as other SPA frameworks.

Nuxt basics: how routing works

Nuxt (v3+) uses file-based routing in the pages/ directory. When a visitor navigates between pages, Nuxt updates the DOM without triggering a full page reload.

Like Next.js and SvelteKit, you need to hook into Nuxt's navigation lifecycle to capture every pageview, not just the initial load.

The Nuxt plugin approach

Nuxt plugins run on the client side and have access to the Nuxt router. The cleanest approach is a client-side plugin that fires a pageview on each route change.

Create plugins/analytics.client.ts (the .client suffix ensures it only runs in the browser):

// plugins/analytics.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  const TRACKING_ID = 'YOUR_TRACKING_ID';
  const INGEST_URL = 'https://www.antlytics.com/api/ingest/pageview';
  const SESSION_KEY = 'ant_sid';

  function getSessionId(): string {
    try {
      let sid = sessionStorage.getItem(SESSION_KEY);
      if (sid) return sid;
      sid = crypto.randomUUID();
      sessionStorage.setItem(SESSION_KEY, sid);
      return sid;
    } catch {
      return crypto.randomUUID();
    }
  }

  function sendPageview(pathname: string) {
    fetch(INGEST_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      keepalive: true,
      body: JSON.stringify({
        tracking_id: TRACKING_ID,
        pathname,
        referrer: document.referrer || undefined,
        session_id: getSessionId()
      })
    }).catch(() => {});
  }

  const router = useRouter();

  // Track initial page load
  nuxtApp.hook('app:mounted', () => {
    sendPageview(router.currentRoute.value.path);
  });

  // Track subsequent navigations
  router.afterEach((to) => {
    sendPageview(to.path);
  });
});

Replace YOUR_TRACKING_ID with your actual UUID from Settings → Tracking Snippet in your Antlytics dashboard.

JavaScript version (without TypeScript)

If you're not using TypeScript, create plugins/analytics.client.js:

// plugins/analytics.client.js
export default defineNuxtPlugin((nuxtApp) => {
  const TRACKING_ID = 'YOUR_TRACKING_ID';
  const INGEST_URL = 'https://www.antlytics.com/api/ingest/pageview';
  const SESSION_KEY = 'ant_sid';

  function getSessionId() {
    try {
      let sid = sessionStorage.getItem(SESSION_KEY);
      if (sid) return sid;
      sid = crypto.randomUUID();
      sessionStorage.setItem(SESSION_KEY, sid);
      return sid;
    } catch {
      return crypto.randomUUID();
    }
  }

  function sendPageview(pathname) {
    fetch(INGEST_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      keepalive: true,
      body: JSON.stringify({
        tracking_id: TRACKING_ID,
        pathname,
        referrer: document.referrer || undefined,
        session_id: getSessionId()
      })
    }).catch(() => {});
  }

  const router = useRouter();

  nuxtApp.hook('app:mounted', () => {
    sendPageview(router.currentRoute.value.path);
  });

  router.afterEach((to) => {
    sendPageview(to.path);
  });
});

Verifying it works

  1. Start your Nuxt dev server (npm run dev or pnpm dev).
  2. Open browser dev tools → Network tab.
  3. Navigate between pages.
  4. Look for POST requests to api/ingest/pageview on each navigation.
  5. Check your Antlytics dashboard for new pageviews.

Nuxt 3 note: The .client plugin suffix ensures the plugin only runs on the client side. Do not remove it — the analytics code references document, sessionStorage, and fetch, none of which are available during server-side rendering.

Nuxt 3 specifics

This guide is written for Nuxt 3+ (the current stable major version). Nuxt 2 uses a different plugin format (export default function({ app }) {...}) and the app.router.afterEach approach instead of useRouter(). If you're on Nuxt 2, check the Nuxt 2 docs for the equivalent lifecycle hooks.

VERIFY_IN_REPO: If Antlytics ships a dedicated Nuxt module or npm package, check the implementation guides for updated instructions.

First-party proxy with Nuxt

To route analytics requests through your own domain (avoiding ad blocker filtering), create a Nuxt server route in server/routes/api/antlytics/pageview.post.ts:

// server/routes/api/antlytics/pageview.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event);

  const response = await fetch('https://www.antlytics.com/api/ingest/pageview', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
  });

  return response.status;
});

Then update your plugin's INGEST_URL to /api/antlytics/pageview.

FAQ

Does this work with Nuxt's SSR mode? Yes. The .client plugin runs after hydration in the browser. SSR handles the initial HTML; the plugin handles subsequent client-side analytics.

Does this work with Nuxt's static generation mode? Yes. Statically generated pages are served as HTML and hydrated by Vue in the browser. The plugin runs after hydration and tracks all navigations.

Can I use the script tag approach instead of a plugin? Yes. Add the Antlytics script tag to your nuxt.config.ts via the head configuration:

export default defineNuxtConfig({
  app: {
    head: {
      script: [
        { innerHTML: `/* your tracking snippet */`, type: 'text/javascript' }
      ]
    }
  }
});

The plugin approach is cleaner because it uses Nuxt's router events and avoids the pushState wrapping complexity.


Related: SPA analytics: tracking single-page apps · SvelteKit analytics · Antlytics implementation guides