Nuxt Integration
SkillCloud & infraNuxt adapter for embedding emulators directly in a Nuxt app via @emulators/adapter-nuxt. Use when the user needs to embed emulators in Nuxt, set up same-origin OAuth for preview deployments, create an emulate catch-all server route, configure persistence for embedded Nuxt emulators, or wrap nuxt.config with withEmulate. Triggers include "Nuxt emulator", "adapter-nuxt", "embedded emulator", "same-origin OAuth", "createEmulateHandler", "withEmulate", or any task requiring emulators inside a Nuxt app.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Nuxt Integration skill
What this skill tells your AI
The instructions your AI receives, as published by vercel-labs/emulate in skills/nuxt/SKILL.md and read by ahel’s review.
The @emulators/adapter-nuxt package embeds emulators directly into a Nuxt app, running them on the same origin. This is useful for preview deployments where OAuth callback URLs change with every deployment.
Install
npm install @emulators/adapter-nuxt @emulators/github @emulators/google
Only install the emulators you need. Each @emulators/* package is published independently, keeping server bundles small.
Server Route
Create a named catch-all route that serves emulator traffic:
// server/routes/emulate/[...path].ts
import { createEmulateHandler } from '@emulators/adapter-nuxt'
import * as github from '@emulators/github'
import * as google from '@emulators/google'
export default defineEventHandler(createEmulateHandler({
services: {
github: {
emulator: github,
seed: {
users: [{ login: 'octocat', name: 'The Octocat' }],
repos: [{ owner: 'octocat', name: 'hello-world', auto_init: true }],
},
},
google: {
emulator: google,
seed: {
users: [{ email: 'test@example.com', name: 'Test User' }],
},
},
},
}))
This creates these routes:
/emulate/github/**serves the GitHub emulator/emulate/google/**serves the Google emulator
GitHub installation-token metadata is available server-side at /emulate/github/_emulate/installation-tokens.
Nuxt Config
Emulator UI pages use bundled fonts. Wrap your Nuxt config so Nitro traces the core package assets into production builds:
// nuxt.config.ts
import { withEmulate } from '@emulators/adapter-nuxt'
export default defineNuxtConfig(withEmulate({
// your normal Nuxt config
}))
OAuth Configuration
Point your OAuth provider at the emulator paths on the same origin:
const baseUrl = process.env.NUXT_PUBLIC_SITE_URL ?? 'http://localhost:3000'
export const githubOAuth = {
clientId: 'any-value',
clientSecret: 'any-value',
authorizationUrl: `${baseUrl}/emulate/github/login/oauth/authorize`,
tokenUrl: `${baseUrl}/emulate/github/login/oauth/access_token`,
userInfoUrl: `${baseUrl}/emulate/github/user`,
}
No oauth_apps need to be seeded. When none are configured, the emulator skips client_id, client_secret, and redirect_uri validation.
Persistence
By default, emulator state is in-memory and resets on every cold start. To persist state across restarts, pass a persistence adapter.
Nitro Storage
import { createEmulateHandler } from '@emulators/adapter-nuxt'
import * as github from '@emulators/github'
const storageAdapter = {
async load() { return await useStorage('emulate').getItem<string>('state') },
async save(data: string) { await useStorage('emulate').setItem('state', data) },
}
export default defineEventHandler(createEmulateHandler({
services: { github: { emulator: github } },
persistence: storageAdapter,
}))
File Persistence
For local development, @emulators/core ships a file-based adapter:
import { filePersistence } from '@emulators/core'
persistence: filePersistence('.emulate/state.json'),
GitHub App seeds may omit private_key. Retain the handler and call server-only generatedSecrets(); explicit keys are excluded. Keep persisted snapshots private and implement initialize atomically.
How Persistence Works
- Cold start: The adapter loads state from the persistence adapter. If found, it restores the full Store and token map. If not found, it seeds from config and saves the initial state.
- After mutating requests (POST, PUT, PATCH, DELETE): State is saved. Saves are serialized via an internal queue to prevent race conditions.
- No persistence configured: Falls back to pure in-memory. Seed data re-initializes on every cold start.
How It Works
- Incoming request:
/emulate/github/login/oauth/authorize?client_id=... - Parse: service =
github, rest =/login/oauth/authorize - Strip prefix: A new
Requestis created with the stripped path and forwarded to the GitHub service app - Rewrite response: HTML
actionandhrefattributes, CSSurl()font references, andLocationheaders get the service prefix prepended - Persist: After mutating requests, state is saved via the persistence adapter
Limitations
- Requires a Node-compatible Nuxt server runtime since emulators use Node APIs
- Concurrent mutations use last-write-wins semantics. Generated identities require
initializeto select the initial snapshot atomically across cold starts.
Config Reference
createEmulateHandler(config, options?)
| Field | Type | Description |
|---|---|---|
services | Record<string, EmulatorEntry> | Map of service name to emulator config |
persistence? | PersistenceAdapter | Optional persistence adapter for state across cold starts |
Each EmulatorEntry:
| Field | Type | Description |
|---|---|---|
emulator | EmulatorModule | The emulator package, such as import * as github from '@emulators/github' |
seed? | Record<string, unknown> | Seed data matching the service's config schema |
Options:
| Option | Type | Default | Description |
|---|---|---|---|
param | string | "path" | Named catch-all route param |
routePrefix | string | detected | Path prefix where the catch-all route is mounted |
withEmulate(nuxtConfig)
Wraps a Nuxt config to include @emulators/core assets in Nitro's production trace. Call it inside defineNuxtConfig in nuxt.config.ts.
PersistenceAdapter
interface PersistenceAdapter {
load(): Promise<string | null>
save(data: string): Promise<void>
initialize?(data: string): Promise<string>
}
initialize must atomically create the initial value or return the value another instance created first. Implement it with compare-and-set semantics such as Redis SET NX. The built-in filePersistence(path) from @emulators/core provides this behavior for local development.
Signals
- GitHub stars
- 2k
- Forks
- 124
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
nuxt-vercel-labs- Source
- github.com/vercel-labs/emulate