vercel-labs/next
Next.js adapter for embedding emulators directly in a Next.js app via @emulators/adapter-next. Use when the user needs to embed emulators in Next.js, set up same-origin OAuth for Vercel preview deployments, create an emulate catch-all route handler, configure Auth.js/NextAuth with embedded emulators, add persistence to embedded emulators, or wrap next.config with withEmulate. Triggers include "Next.js emulator", "adapter-next", "embedded emulator", "same-origin OAuth", "Vercel preview", "createEmulateHandler", "withEmulate", or any task requiring emulators inside a Next.js app.
npx skills add https://github.com/vercel-labs/json-render --skill next
The @emulators/adapter-next package embeds emulators directly into a Next.js App Router app, running them on the same origin. This is particularly useful for Vercel preview deployments where OAuth callback URLs change with every deployment.
npm install @emulators/adapter-next @emulators/github @emulators/google
Only install the emulators you need. Each @emulators/* package is published independently, keeping serverless bundles small.
Create a catch-all route that serves emulator traffic:
// app/emulate/[...path]/route.ts
import { createEmulateHandler } from '@emulators/adapter-next'
import * as github from '@emulators/github'
import * as google from '@emulators/google'
export const { GET, POST, PUT, PATCH, DELETE } = 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: '[email protected]', name: 'Test User' }],
},
},
},
})
This creates the following routes:
/emulate/github/** serves the GitHub emulator/emulate/google/** serves the Google emulatorPoint your provider at the emulator paths on the same origin:
import GitHub from 'next-auth/providers/github'
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: 'http://localhost:3000'
GitHub({
clientId: 'any-value',
clientSecret: 'any-value',
authorization: { url: `${baseUrl}/emulate/github/login/oauth/authorize` },
token: { url: `${baseUrl}/emulate/github/login/oauth/access_token` },
userinfo: { url: `${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.
Emulator UI pages use bundled fonts. Wrap your Next.js config to include them in the serverless trace:
// next.config.mjs
import { withEmulate } from '@emulators/adapter-next'
export default withEmulate({
// your normal Next.js config
})
If you mount the catch-all at a custom path, pass the matching prefix:
export default withEmulate(nextConfig, { routePrefix: '/api/emulate' })
By default, emulator state is in-memory and resets on every cold start. To persist state across restarts, pass a persistence adapter.
import { createEmulateHandler } from '@emulators/adapter-next'
import * as github from '@emulators/github'
const kvAdapter = {
async load() { return await kv.get('emulate-state') },
async save(data: string) { await kv.set('emulate-state', data) },
}
export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({
services: { github: { emulator: github } },
persistence: kvAdapter,
})
For local development, @emulators/core ships a file-based adapter:
import { filePersistence } from '@emulators/core'
// persists to a JSON file
persistence: filePersistence('.emulate/state.json'),
/emulate/github/login/oauth/authorize?client_id=...github, rest = /login/oauth/authorizeRequest is created with the stripped path and forwarded to the GitHub service appaction and href attributes, CSS url() font references, and Location headers get the service prefix prependedcrypto.randomBytescreateEmulateHandler(config)| 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 (e.g. import * as github from '@emulators/github') |
| seed? | Record<string, unknown> | Seed data matching the service's config schema |
withEmulate(nextConfig, options?)Wraps a Next.js config to include emulator font files in the serverless output trace. Call it around your exported config in next.config.mjs or next.config.ts.
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| routePrefix | string | "/emulate" | The path prefix where the catch-all route is mounted |
PersistenceAdapterinterface PersistenceAdapter {
load(): Promise<string | null>
save(data: string): Promise<void>
}
The built-in filePersistence(path) from @emulators/core provides a file-based adapter for local development.
Take vercel-labs/next from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npm, npx.
Without those the skill loads but fails at the first command.