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.
connectionTracking: true and only works with sse or ws update strategies.Enable route tracking in your Nuxt config:
export default defineNuxtConfig({
skewProtection: {
connectionTracking: true,
routeTracking: true
}
})
Stats are only sent to connections that pass authorization. Implement the skew:authorize-stats hook:
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()
}
})
})
For live updates, use the useActiveConnections composable on authorized pages:
<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>
For non-WebSocket access (CI/CD, external tools), expose stats via an API endpoint:
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
}
Force users on a specific page to refresh when you deploy critical changes to that page.
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 })
}
}
}
})
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 }
})
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"}'
Route tracking sends a message to the server on every client-side navigation. For high-traffic sites: