@emulators/adapter-next
v0.12.2
Published
Next.js App Router integration for emulate. Embed emulators directly in your Next.js app so they run on the same origin, solving the Vercel preview deployment problem where OAuth callback URLs change with every deployment.
Readme
@emulators/adapter-next
Next.js App Router integration for emulate. Embed emulators directly in your Next.js app so they run on the same origin, solving the Vercel preview deployment problem where OAuth callback URLs change with every deployment.
Part of emulate — local drop-in replacement services for CI and no-network sandboxes.
Install
npm install @emulators/adapter-nextOnly install the emulators you need alongside the adapter:
npm install @emulators/adapter-next @emulators/github @emulators/googleRoute handler
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 emulator = 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' }],
},
},
},
})
export const { GET, POST, PUT, PATCH, DELETE, OPTIONS } = emulatorGitHub App seeds may omit private_key. Read generated keys with the handler's server-only generatedSecrets() method. Explicit keys are excluded; persisted snapshots contain generated keys and require a private backend with atomic initialize.
Inspect minted installation-token metadata at /emulate/github/_emulate/installation-tokens.
Auth.js / NextAuth configuration
Point 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.
Font files in serverless
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' })Persistence
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, OPTIONS } = createEmulateHandler({
services: { github: { emulator: github } },
persistence: kvAdapter,
})For local development, @emulators/core ships filePersistence:
import { filePersistence } from '@emulators/core'
// ...
persistence: filePersistence('.emulate/state.json'),Links
Custom emulators
Pass a definition created with defineEmulator from emulate to services[name].emulator in framework adapters. The same definition runs through the CLI and createEmulator({ service: definition, listen: false }) in tests. Custom entries accept an optional inspector and persistence adapter. Export OPTIONS so preflight requests and custom OPTIONS routes reach the emulator. Root-relative redirects stay under the service mount, while custom HTML bodies pass through unchanged. Keep state inside the definition's state factory, and await the handler's close() in tests. See the custom emulator guide.
