---
title: "v2.0.0"
description: "Release notes for Nuxt Skew Protection v2."
canonical_url: "https://nuxtseo.com/docs/skew-protection/releases/v2"
---

## Introduction

Nuxt Skew Protection v2 supports Nuxt 4.6 and Nuxt 5. It uses Nuxt's native polling by default, so no tab holds a persistent connection.

v2 ships in [Nuxt SEO v6](/docs/nuxt-seo/releases/v6). Nuxt SEO v6 installs Skew Protection by default.

Check the [breaking changes](#breaking-changes) before you upgrade. For a guided migration, use the [LLM upgrade prompt](#llm-upgrade-prompt) with Claude or [ChatGPT](https://chatgpt.com).

## 📦 Included in Nuxt SEO v6

`@nuxtjs/seo` v6 installs this module with two default overrides:

```ts
const nuxtSeoDefaults = { skewProtection: { updateStrategy: 'polling', cookie: false } }
```

- `updateStrategy: 'polling'` is the same as the v2 default.
- `cookie: false` sets no version cookie. Without the cookie, `isClientOutdated(event)`{lang="ts"} always returns `false`.

Your own `skewProtection` options take precedence. To remove the module from Nuxt SEO, set `skewProtection: false`.

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  modules: ['@nuxtjs/seo'],
  skewProtection: false,
})
```

Asset retention needs build history. Keep `node_modules/.cache/nuxt-seo/skew-protection` between CI builds, or configure a shared [storage driver](/docs/skew-protection/guides/storage-configuration).

## 🔌 Polling by Default

v1 selected the update strategy from the Nitro preset. Most server presets used Server-Sent Events (SSE), so every open tab held a connection.

v2 uses Nuxt's native manifest polling on every platform. Nuxt checks `/_nuxt/builds/latest.json` once per hour by default. The module adds no polling timer. ([#83](https://github.com/harlan-zw/nuxt-skew-protection/pull/83))

For instant updates, select a push strategy:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  skewProtection: {
    updateStrategy: 'sse', // or 'ws', pusherAdapter(), ablyAdapter()
  },
})
```

## 🗂️ One Push Connection per Browser

If you select a push strategy, visible tabs share one connection. Web Locks select the owner tab without heartbeat timers. ([#83](https://github.com/harlan-zw/nuxt-skew-protection/pull/83))

- Hidden tabs release ownership and request no connection.
- If the owner tab closes or hides, a waiting visible tab takes the connection.
- Every tab still receives version updates through `BroadcastChannel`.

Browsers without Web Locks or `BroadcastChannel` keep one connection per tab. `connectionTracking: true` also keeps one connection per tab.

## 🧩 Nuxt 4.6 and Nuxt 5

v2 uses portable server APIs from `nuxt/server` and native `fetch`. It adds typed entries for app and server functions. ([#77](https://github.com/harlan-zw/nuxt-skew-protection/pull/77))

```ts
import { useSkewProtection } from '#skew-protection/app'
import { isClientOutdated } from '#skew-protection/server'
```

Auto imports still work. The `nuxt-skew-protection/server` alias also still works.

## 🪶 Fewer Dependencies

v2 removes the `ofetch`, `pkg-types`, and `tinyexec` dependencies. `nuxt-site-config` and `@nuxtjs/robots` become optional peers. ([#77](https://github.com/harlan-zw/nuxt-skew-protection/pull/77))

v2 also removes the `zod` dependency. The Pusher and Ably adapters parse their config without it, so `pusherConfigSchema` and `ablyConfigSchema` become `parsePusherConfig` and `parseAblyConfig`. A custom adapter that imports zod must install it. ([#94](https://github.com/harlan-zw/nuxt-skew-protection/pull/94))

## 🗓️ Since v1

These changes shipped in v1.5.0 to v1.6.2. Nuxt SEO v6 users get them for the first time.

- v1.5.0 records chunk metadata without asset bundling. ([#43](https://github.com/harlan-zw/nuxt-skew-protection/pull/43))
- v1.5.0 turns off asset storage when native Vercel Skew Protection is active. ([#44](https://github.com/harlan-zw/nuxt-skew-protection/pull/44))
- v1.5.0 keeps adapter secrets out of client bundles. ([#38](https://github.com/harlan-zw/nuxt-skew-protection/pull/38))
- v1.6.0 loads custom realtime adapters through `defineAdapter`. See [External Providers](/docs/skew-protection/providers/external). ([#68](https://github.com/harlan-zw/nuxt-skew-protection/pull/68))
- v1.6.0 ships an Agent Skill in the package. ([#62](https://github.com/harlan-zw/nuxt-skew-protection/pull/62))
- v1.6.0 makes `reloadStrategy: 'idle'` wait for user inactivity. ([#69](https://github.com/harlan-zw/nuxt-skew-protection/pull/69))
- v1.6.2 keeps existing service workers, such as a PWA worker. ([#75](https://github.com/harlan-zw/nuxt-skew-protection/pull/75))

## ⚠️ Breaking Changes

### Nuxt 4.6 and Node 22

v2 requires Nuxt `^4.6.0 || ^5.0.0`. v1 accepted Nuxt `>=4.0.0`.

v2 requires Node `^22.22.3 || ^24.15.0 || >=26.0.0`. ([#77](https://github.com/harlan-zw/nuxt-skew-protection/pull/77))

### Default `updateStrategy` Is `'polling'`

If you do not set `updateStrategy`, v2 polls on every platform. Open tabs see a new deployment within the Nuxt check interval. ([#83](https://github.com/harlan-zw/nuxt-skew-protection/pull/83))

v1 used `'sse'` on most server presets and `'ws'` on `cloudflare-durable`. To keep push updates, set the strategy explicitly.

```ts
// Before (v1): SSE on Node.js, Vercel, and Netlify without config
export default defineNuxtConfig({
  modules: ['nuxt-skew-protection'],
})

// After (v2): select SSE explicitly
export default defineNuxtConfig({
  modules: ['nuxt-skew-protection'],
  skewProtection: {
    updateStrategy: 'sse', // use 'ws' on cloudflare-durable
  },
})
```

`connectionTracking` and `useActiveConnections()`{lang="ts"} need `'sse'` or `'ws'`. If you use them, set `updateStrategy` explicitly.

To keep polling with faster checks, shorten the Nuxt interval:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  experimental: {
    checkOutdatedBuildInterval: 5 * 60 * 1000, // 5 minutes
  },
})
```

### `experimental.appManifest: false` Throws

The module needs deployment manifests for its asset metadata. If you disable the Nuxt app manifest, v2 stops the build. ([#83](https://github.com/harlan-zw/nuxt-skew-protection/pull/83))

```
nuxt-skew-protection requires experimental.appManifest. Enable it or disable the module.
```

```ts
// Before (v1): the module ran without the app manifest
export default defineNuxtConfig({
  experimental: { appManifest: false },
})

// After (v2): remove the override, or disable the module
export default defineNuxtConfig({
  skewProtection: { enabled: false },
})
```

### Shared Push Connections

If you select a push strategy, tabs share one connection by default. This changes the state each tab sees. ([#83](https://github.com/harlan-zw/nuxt-skew-protection/pull/83))

- Hidden tabs pause their subscription.
- Connection ids, statistics, and `send()`{lang="ts"} calls belong to the owner tab.
- `isConnected` is `true` in visible subscribed tabs that do not own the connection.

To keep one connection per tab, set `multiTab: false` or enable `connectionTracking`.

```ts
// Before (v1): one SSE connection per tab
const before = { skewProtection: { updateStrategy: 'sse' } }

// After (v2): opt out of the shared connection
const after = { skewProtection: { updateStrategy: 'sse', multiTab: false } }
```

### Default Storage Driver Is `fs-lite`

The default storage driver changes from `fs` to `fs-lite`. The base directory and the file paths stay the same. ([#77](https://github.com/harlan-zw/nuxt-skew-protection/pull/77))

```ts
// Before (v1) default
const before = { storage: { driver: 'fs', base: 'node_modules/.cache/nuxt-seo/skew-protection' } }

// After (v2) default
const after = { storage: { driver: 'fs-lite', base: 'node_modules/.cache/nuxt-seo/skew-protection' } }
```

To keep the `fs` driver, set `storage.driver: 'fs'`.

### Robots and Site Config Are Optional

v1 installed `@nuxtjs/robots` (`>=5.6.7`) and `nuxt-site-config` for you. v2 makes both optional peers: Robots `^7.0.0` and Site Config `^5.0.0`. ([#77](https://github.com/harlan-zw/nuxt-skew-protection/pull/77))

- Without Nuxt Robots, SSE and [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) connections do not filter bot traffic.
- Without Site Config, debug output uses the request origin.

To keep bot filtering, install Nuxt Robots:

```bash
npx nuxt module add @nuxtjs/robots
```

`@nuxtjs/seo` installs both modules, so Nuxt SEO users need no change.

### DevTools Panel Needs `nuxtseo-devtools-host`

The DevTools panel loads from the shared `nuxtseo-devtools-host` package. Without it, the Skew Protection tab shows an install button. ([#77](https://github.com/harlan-zw/nuxt-skew-protection/pull/77))

```bash
pnpm add -D nuxtseo-devtools-host
```

### Nitro Hook Payload Types

The `skew:connection:*` and `skew:subscribe-stats` payloads gain an optional `peer`. The `event` field of `skew:subscribe-stats` and `skew:authorize-stats` also accepts `RequestEvent` from `nuxt/server`. ([#77](https://github.com/harlan-zw/nuxt-skew-protection/pull/77))

If a typed hook handler expects only `H3Event`, widen its type.

## 🤖 LLM Upgrade Prompt

Paste this into Claude, ChatGPT, or any LLM to migrate your codebase.

::collapsible{name="LLM Upgrade Prompt"}
~~~markdown
# Upgrade Guide: nuxt-skew-protection v1 to v2.0.0

You are helping me upgrade nuxt-skew-protection from v1 to v2.0.0 in my Nuxt project.

## Requirements

- Nuxt `^4.6.0 || ^5.0.0`
- Node `^22.22.3 || ^24.15.0 || >=26.0.0`

If my project uses `@nuxtjs/seo`, upgrade `@nuxtjs/seo` to v6 instead. It installs this module with `updateStrategy: 'polling'` and `cookie: false`.

## Breaking Changes

### 1. Default update strategy is polling

**What changed:** An unset `updateStrategy` polls on every platform. v1 used SSE on most server presets and WebSockets on `cloudflare-durable`.

**Search my codebase for:** `skewProtection`, `connectionTracking`, `useActiveConnections`

**Regex pattern:** `skewProtection\s*:|connectionTracking|useActiveConnections`

**Before:**
```ts
skewProtection: { connectionTracking: true }
```

**After:**
```ts
skewProtection: { updateStrategy: 'sse', connectionTracking: true } // 'ws' on cloudflare-durable
```

**Action:** If the app needs instant updates or uses connection tracking, set `updateStrategy` explicitly. Otherwise, make no change.

### 2. `experimental.appManifest: false` throws

**What changed:** The build fails if the project disables the Nuxt app manifest.

**Search my codebase for:** `appManifest`

**Regex pattern:** `appManifest\s*:\s*false`

**Before:**
```ts
experimental: { appManifest: false }
```

**After:**
```ts
// remove the override, or disable the module:
skewProtection: { enabled: false }
```

### 3. Tabs share push connections

**What changed:** With a push strategy and `multiTab: true` (default), visible tabs share one connection. Hidden tabs pause. Connection ids, statistics, and `send()`{lang="ts"} belong to the owner tab.

**Search my codebase for:** `$skewConnection`, `skew:connection:open`, `isConnected`

**Regex pattern:** `\$skewConnection|skew:connection:|isConnected`

**Action:** If code needs one connection per tab, set `multiTab: false` or `connectionTracking: true`.

```ts
skewProtection: { updateStrategy: 'sse', multiTab: false }
```

### 4. Default storage driver is `fs-lite`

**What changed:** The default driver changes from `fs` to `fs-lite`. The base directory stays `node_modules/.cache/nuxt-seo/skew-protection`.

**Search my codebase for:** `driver: 'fs'`

**Regex pattern:** `driver:\s*['"]fs['"]`

**Action:** Make no change. Set `storage.driver: 'fs'` to keep the old driver.

### 5. Robots and Site Config are optional peers

**What changed:** The module no longer installs `@nuxtjs/robots` or `nuxt-site-config`. Without Robots, connections do not filter bot traffic.

**Search my codebase for:** `@nuxtjs/robots` in `package.json` and `nuxt.config.ts`

**Action:** To keep bot filtering, run `npx nuxt module add @nuxtjs/robots`. Use Robots v7 and Site Config v5.

### 6. DevTools panel needs a host package

**What changed:** The DevTools panel loads from `nuxtseo-devtools-host`.

**Action:** If I use the panel, run `pnpm add -D nuxtseo-devtools-host`.

### 7. Nitro hook payload types

**What changed:** `skew:connection:*` and `skew:subscribe-stats` payloads gain an optional `peer`. The `event` field of `skew:subscribe-stats` and `skew:authorize-stats` also accepts `RequestEvent` from `nuxt/server`.

**Search my codebase for:** `skew:authorize-stats`, `skew:subscribe-stats`

**Regex pattern:** `skew:(authorize|subscribe)-stats`

**Action:** If a handler types `event` as `H3Event` only, widen the type.

## Verification Checklist

1. `pnpm install` (update lockfile)
2. `npx nuxt typecheck` (catch type errors)
3. `pnpm build` (check that the build does not throw on `appManifest`)
4. `pnpm test` (catch runtime regressions)
5. Deploy twice, then check that `/_nuxt/builds/latest.json` lists both versions

---

Please scan my codebase for all affected patterns listed above and generate a complete migration plan. For each file that needs changes, show me the exact diff.
~~~
::

## ✅ Upgrading

If you use `@nuxtjs/seo`, upgrade it to v6. It includes Skew Protection v2.

```bash
# pnpm
pnpm add nuxt-skew-protection@^2

# npm
npm install nuxt-skew-protection@^2

# yarn
yarn add nuxt-skew-protection@^2
```

## 📋 Changelog

> [Compare changes](https://github.com/harlan-zw/nuxt-skew-protection/compare/v1.6.2...v2.0.0)

### 🚀 Enhancements

- Prepare Skew Protection 2 for Nuxt 4.6 ([#77](https://github.com/harlan-zw/nuxt-skew-protection/pull/77))

### 🔥 Performance

- Default to polling and share push connections ([#83](https://github.com/harlan-zw/nuxt-skew-protection/pull/83))

### 🩹 Fixes

- Align skill metadata with the Agent Skills spec ([#90](https://github.com/harlan-zw/nuxt-skew-protection/pull/90))

### 📖 Documentation

- Add the module icon and unify the README badges ([#78](https://github.com/harlan-zw/nuxt-skew-protection/pull/78))
- Update the Agent Skill for v1.6.2 ([#79](https://github.com/harlan-zw/nuxt-skew-protection/pull/79))
- Rewrite passive voice ([#87](https://github.com/harlan-zw/nuxt-skew-protection/pull/87))
- Replace an unsupported button color ([#88](https://github.com/harlan-zw/nuxt-skew-protection/pull/88))

### 🏡 Chore

- Schedule weekly package maintenance ([#80](https://github.com/harlan-zw/nuxt-skew-protection/pull/80))
- Declare the playground as an ES module ([#89](https://github.com/harlan-zw/nuxt-skew-protection/pull/89))

### ❤️ Contributors

- [Harlan Wilton](https://github.com/harlan-zw)

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
