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
export default defineNuxtConfig({
  skewProtection: {
    connectionTracking: true,
    routeTracking: true
  }
})

Authorization Hook

Stats are only sent to connections that pass authorization. Implement the skew:authorize-stats hook:

server/plugins/skew-auth.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
<script setup>
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
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:

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
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
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:

# 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:

  • Routes are tracked per-connection, not per-pageview
  • Messages are small (~50 bytes per navigation)
  • Consider disabling for sites with very rapid navigation patterns

Limitations

  • Same limitations as connection tracking
  • Route paths only (no query strings or hashes tracked)
  • Client-side navigations only (full page loads reconnect with new route)
  • Route data is server-side only (not exposed to clients for privacy)
Did this page help you?