---
title: "Tracking User Pages · Nuxt Skew Protection · Nuxt SEO"
canonical_url: "https://nuxtseo.com/docs/skew-protection/guides/route-tracking"
last_updated: "2026-08-14T21:05:59.981Z"
meta:
  description: "Monitor which pages users are viewing and invalidate specific routes."
  "og:description": "Monitor which pages users are viewing and invalidate specific routes."
  "og:title": "Tracking User Pages · Nuxt Skew Protection · Nuxt SEO"
---

Nuxt SEO on GitHub

Switch to Skew ProtectionSwitch to Nuxt SEOSwitch to RobotsSwitch to SitemapSwitch to OG ImageSwitch to Schema.orgSwitch to Link CheckerSwitch to SEO UtilsSwitch to Site ConfigSwitch to AI Ready

**Core Concepts**

# **Tracking User Pages**

Track which pages your users are currently viewing in real-time. This enables targeted version invalidation for specific routes without affecting users on other pages.

Route tracking requires `**connectionTracking: true**` and only works with `**sse**` or `**ws**` update strategies.

Route data is only available to authorized connections for privacy. Regular users cannot see which pages other users are viewing.

## Setup

Enable route tracking in your Nuxt config:

nuxt.config.ts

```ts
export default defineNuxtConfig({
  skewProtection: {
    connectionTracking: true,
    routeTracking: true
  }
})
```

**Authorization hook.** The server only sends stats to connections that pass authorization. Implement the `**skew:authorize-stats**` hook:

server/plugins/skew-auth.ts

```ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('skew:authorize-stats', async ({ event, authorize }) => {
    // With nuxt-auth-utils
    const session = await getUserSession(event)
    if (session.user?.role === 'admin') {
      authorize()
    }
  })
})
```

## Real-time Stats via useActiveConnections

For live updates, use the `**useActiveConnections**` composable on authorized pages:

pages/admin/dashboard.vue

```vue
<script setup lang="ts">
const { total, versions, routes, authorized } = useActiveConnections()

const sortedRoutes = computed(() => {
  return Object.entries(routes.value)
    .sort(([, a], [, b]) => b - a)
    .slice(0, 10)
})
</script>

<template>
  <div v-if="authorized" class="live-pages">
    <h3>Most Active Pages ({{ total }} users)</h3>
    <table>
      <tr v-for="[route, count] in sortedRoutes" :key="route">
        <td>{{ route }}</td>
        <td>{{ count }} viewers</td>
      </tr>
    </table>
  </div>
  <div v-else>
    Not authorized for live stats
  </div>
</template>
```

## API Endpoint (Alternative)

For non-WebSocket access (CI/CD, external tools), expose stats via an API endpoint:

server/api/admin/stats.get.ts

```ts
export default defineEventHandler(async (event) => {
  const authHeader = getHeader(event, 'authorization')
  if (authHeader !== `Bearer ${process.env.ADMIN_SECRET}`) {
    throw createError({ statusCode: 401 })
  }

  return new Promise((resolve) => {
    const nitroApp = useNitroApp()
    // @ts-expect-error custom hook
    nitroApp.hooks.callHook('skew:stats', (stats) => {
      resolve(stats)
    })
  })
})
```

The stats object contains:

```ts
interface Stats {
  total: number // Total active connections
  versions: Record<string, number> // Users per build version
  routes: Record<string, number> // Users per route path
}
```

## Invalidating Users on Specific Routes

Force users on a specific page to refresh when you deploy critical changes to that page.

### Server Plugin for Route Invalidation

server/plugins/route-invalidation.ts

```ts
import { defineNitroPlugin } from 'nitropack/runtime'

export default defineNitroPlugin((nitroApp) => {
  const connections = new Map<string, { route: string, send: (data: unknown) => void }>()

  nitroApp.hooks.hook('skew:connection:open', ({ id, route, send }) => {
    connections.set(id, { route: route || '/', send })
  })

  nitroApp.hooks.hook('skew:connection:route-update', ({ id, route }) => {
    const conn = connections.get(id)
    if (conn)
      conn.route = route
  })

  nitroApp.hooks.hook('skew:connection:close', ({ id }) => {
    connections.delete(id)
  })

  // Expose function to invalidate specific routes
  // @ts-expect-error extending global
  globalThis.invalidateRoute = (routePattern: string | RegExp) => {
    const version = useRuntimeConfig().app.buildId
    for (const [, conn] of connections) {
      const matches = typeof routePattern === 'string'
        ? conn.route === routePattern || conn.route.startsWith(routePattern)
        : routePattern.test(conn.route)

      if (matches) {
        conn.send({ type: 'version', version, force: true })
      }
    }
  }
})
```

### API Endpoint for Invalidation

server/api/admin/invalidate-route.post.ts

```ts
export default defineEventHandler(async (event) => {
  const { route, secret } = await readBody(event)

  if (secret !== process.env.ADMIN_SECRET) {
    throw createError({ statusCode: 401 })
  }

  // @ts-expect-error global function from plugin
  globalThis.invalidateRoute?.(route)

  return { ok: true, route }
})
```

### Usage from CI/CD

After deploying changes to a specific page:

```bash
# Invalidate all users on /checkout
curl -X POST https://yoursite.com/api/admin/invalidate-route \
 -H "Content-Type: application/json" \
 -d '{"route": "/checkout", "secret": "your-admin-secret"}'

# Invalidate all users on /blog/* routes
curl -X POST https://yoursite.com/api/admin/invalidate-route \
 -H "Content-Type: application/json" \
 -d '{"route": "/blog/", "secret": "your-admin-secret"}'
```

## Performance Considerations

Route tracking sends a message to the server on every client-side navigation. For high-traffic sites:

- The module tracks routes per-connection, not per-pageview
- Messages are small (\~50 bytes per navigation)
- Consider disabling for sites with rapid navigation patterns

## Limitations

- Same limitations as [**~~connection tracking~~**](https://nuxtseo.com/docs/skew-protection/guides/live-connections#limitations)
- Route paths only (the module does not track query strings or hashes)
- Client-side navigations only (full page loads reconnect with new route)
- Route data is server-side only (not exposed to clients for privacy)

**Was this page helpful?**

### **Related **

[**Configuration**](https://nuxtseo.com/docs/skew-protection/api/config)

[**Nitro Hooks**](https://nuxtseo.com/docs/skew-protection/nitro-api/nitro-hooks)

[**View Active Connections** Monitor active connections and version distribution in real-time.](https://nuxtseo.com/docs/skew-protection/guides/live-connections) [**useSkewProtection()** Composable for accessing skew protection state and methods.](https://nuxtseo.com/docs/skew-protection/api/use-skew-protection)