Index of every page on this site: llms.txt

Adapters connect your app to external WebSocket services for real-time update notifications. Set an adapter through updateStrategy, including on static/prerendered sites.

Why Use External Providers?

Use an external provider when your host cannot keep connections open or you want the provider to manage them. Your app subscribes through the provider, so static sites can receive updates too.

Available Adapters

AdapterServiceFree Tier
Pusherpusher.com200k messages/day
Ablyably.com6M messages/month

Pusher

Pusher Channels is a hosted WebSocket service.

npx nypm add pusher-js

Configuration:

nuxt.config.ts
import { pusherAdapter } from 'nuxt-skew-protection/adapters/pusher'

export default defineNuxtConfig({
  skewProtection: {
    updateStrategy: pusherAdapter({
      // Required
      key: process.env.PUSHER_KEY,
      cluster: process.env.PUSHER_CLUSTER,
      appId: process.env.PUSHER_APP_ID,
      secret: process.env.PUSHER_SECRET,
      // Optional
      channel: 'skew-protection', // default
      event: 'VersionUpdated', // default
    })
  }
})

Ably

Ably is a realtime messaging platform.

npx nypm add ably

Configuration:

nuxt.config.ts
import { ablyAdapter } from 'nuxt-skew-protection/adapters/ably'

export default defineNuxtConfig({
  skewProtection: {
    updateStrategy: ablyAdapter({
      // Required
      key: process.env.ABLY_KEY,
      authUrl: '/api/ably-token',
      // Optional
      clientId: 'browser',
      channel: 'skew-protection', // default
      event: 'VersionUpdated', // default
    })
  }
})

Custom Adapters

An adapter has two halves. The client half is a module that the module bundles into the browser build. The build half lives in nuxt.config and broadcasts each new build id after a production build. Pusher and Ably use the same mechanism.

Write the client half first. It must export subscribe:

adapter/web.ts
import { defineWebSubscribe } from 'nuxt-skew-protection/adapters'

export const subscribe = defineWebSubscribe<{ url: string }>((config, onMessage) => {
  const socket = new WebSocket(config.url)
  socket.onmessage = event => onMessage({ version: JSON.parse(event.data).version })
  return () => socket.close()
})

Then define the adapter. web is an absolute path, an alias such as ~/adapter/web, or a package specifier.

schema accepts a synchronous Standard Schema, such as a zod or Valibot object. It also accepts a function that returns { _tag: 'Ok', config } or { _tag: 'Err', issues }. The module does not include zod, so install it in your project to use it:

adapter/index.ts
import { fileURLToPath } from 'node:url'
import { defineAdapter } from 'nuxt-skew-protection/adapters'
import { z } from 'zod'

export const myAdapter = defineAdapter({
  name: 'my-adapter',
  schema: z.object({ url: z.string(), token: z.string() }),
  // Only this object reaches the browser. Keep secrets out of it.
  toPublicConfig: ({ url }) => ({ url }),
  web: fileURLToPath(new URL('./web', import.meta.url)),
  // Packages that `web` imports. The build fails if one is missing.
  dependencies: [],
  broadcast: async ({ url, token }, version) => {
    await fetch(url.replace('wss:', 'https:'), {
      method: 'POST',
      headers: { authorization: `Bearer ${token}` },
      body: JSON.stringify({ version }),
    })
  },
})
nuxt.config.ts
import { myAdapter } from './adapter'

export default defineNuxtConfig({
  modules: ['nuxt-skew-protection'],
  skewProtection: {
    updateStrategy: myAdapter({ url: 'wss://example.com/skew', token: process.env.SKEW_TOKEN! }),
  },
})

The build validates the config with schema, then calls broadcast once the production build finishes. A failed broadcast logs an error and does not fail the build.

Was this page helpful?