next
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNext.js Integration
Next.js 集成
The 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.
@emulators/adapter-next@emulators/adapter-nextInstall
安装
bash
npm install @emulators/adapter-next @emulators/github @emulators/googleOnly install the emulators you need. Each package is published independently, keeping serverless bundles small.
@emulators/*bash
npm install @emulators/adapter-next @emulators/github @emulators/google仅安装你需要的模拟器。每个包都是独立发布的,从而保持无服务器包体积小巧。
@emulators/*Route Handler
路由处理器
Create a catch-all route that serves emulator traffic:
typescript
// 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: 'test@example.com', name: 'Test User' }],
},
},
},
})This creates the following routes:
- serves the GitHub emulator
/emulate/github/** - serves the Google emulator
/emulate/google/**
创建一个兜底路由来处理模拟器流量:
typescript
// 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: 'test@example.com', name: 'Test User' }],
},
},
},
})这将创建以下路由:
- 用于提供GitHub模拟器服务
/emulate/github/** - 用于提供Google模拟器服务
/emulate/google/**
Auth.js / NextAuth Configuration
Auth.js / NextAuth 配置
Point your provider at the emulator paths on the same origin:
typescript
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 need to be seeded. When none are configured, the emulator skips , , and validation.
oauth_appsclient_idclient_secretredirect_uri将你的提供者指向同源环境下的模拟器路径:
typescript
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` },
})无需预填充数据。当未配置任何时,模拟器会跳过、和的验证。
oauth_appsoauth_appsclient_idclient_secretredirect_uriFont Tracing for Serverless
无服务器环境下的字体追踪
Emulator UI pages use bundled fonts. Wrap your Next.js config to include them in the serverless trace:
typescript
// 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:
typescript
export default withEmulate(nextConfig, { routePrefix: '/api/emulate' })模拟器UI页面使用打包后的字体。请包装你的Next.js配置,将这些字体包含在无服务器追踪范围内:
typescript
// next.config.mjs
import { withEmulate } from '@emulators/adapter-next'
export default withEmulate({
// 你的常规Next.js配置
})如果你将兜底路由挂载在自定义路径下,请传入匹配的前缀:
typescript
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 adapter.
persistence默认情况下,模拟器状态存储在内存中,每次冷启动时都会重置。若要在重启后保留状态,请传入一个适配器。
persistenceCustom Adapter (Vercel KV, Redis, etc.)
自定义适配器(Vercel KV、Redis等)
typescript
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,
})typescript
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,
})File Persistence (Local Dev)
文件持久化(本地开发)
For local development, ships a file-based adapter:
@emulators/coretypescript
import { filePersistence } from '@emulators/core'
// persists to a JSON file
persistence: filePersistence('.emulate/state.json'),针对本地开发,提供了一个基于文件的适配器:
@emulators/coretypescript
import { filePersistence } from '@emulators/core'
// 持久化到JSON文件
persistence: filePersistence('.emulate/state.json'),How Persistence Works
持久化工作原理
- Cold start: The adapter loads state from the persistence adapter. If found, it restores the full Store and token map (skipping seed). 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.
- 冷启动:适配器从持久化加载状态。如果找到状态,则恢复完整的Store和令牌映射(跳过预填充数据)。如果未找到,则从配置中预填充数据并保存初始状态。
- 变更请求后(POST、PUT、PATCH、DELETE):状态会被保存。保存操作通过内部队列序列化,以防止竞争条件。
- 未配置持久化:回退到纯内存存储。预填充数据会在每次冷启动时重新初始化。
How It Works
工作原理
- Incoming request:
/emulate/github/login/oauth/authorize?client_id=... - Parse: service = , rest =
github/login/oauth/authorize - Strip prefix: A new is created with the stripped path and forwarded to the GitHub Hono app
Request - Rewrite response: HTML and
actionattributes, CSShreffont references, andurl()headers get the service prefix prependedLocation - Persist: After mutating requests, state is saved via the persistence adapter
- 传入请求:
/emulate/github/login/oauth/authorize?client_id=... - 解析:服务 = ,剩余路径 =
github/login/oauth/authorize - 移除前缀:创建一个新的对象,移除前缀后转发到GitHub Hono应用
Request - 重写响应:HTML的和
action属性、CSS的href字体引用,以及url()头都会被添加服务前缀Location - 持久化:变更请求完成后,状态通过持久化适配器保存
Limitations
限制
- Requires the Node.js runtime (not Edge) since emulators use
crypto.randomBytes - Concurrent serverless instances writing to the same persistence adapter use last-write-wins semantics (acceptable for dev/preview traffic)
- 需要Node.js运行时(不支持Edge),因为模拟器使用
crypto.randomBytes - 多个无服务器实例同时写入同一个持久化适配器时,采用最后写入优先的语义(适用于开发/预览流量)
Config Reference
配置参考
createEmulateHandler(config)
createEmulateHandler(config)createEmulateHandler(config)
createEmulateHandler(config)| Field | Type | Description |
|---|---|---|
| | Map of service name to emulator config |
| | Optional persistence adapter for state across cold starts |
Each :
EmulatorEntry| Field | Type | Description |
|---|---|---|
| | The emulator package (e.g. |
| | Seed data matching the service's config schema |
| 字段 | 类型 | 描述 |
|---|---|---|
| | 服务名称到模拟器配置的映射 |
| | 可选的持久化适配器,用于跨冷启动保留状态 |
每个:
EmulatorEntry| 字段 | 类型 | 描述 |
|---|---|---|
| | 模拟器包(例如 |
| | 符合服务配置 schema 的预填充数据 |
withEmulate(nextConfig, options?)
withEmulate(nextConfig, options?)withEmulate(nextConfig, options?)
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 or .
next.config.mjsnext.config.ts| Option | Type | Default | Description |
|---|---|---|---|
| | | The path prefix where the catch-all route is mounted |
包装Next.js配置,将模拟器字体文件包含在无服务器输出追踪中。在或中,将导出的配置用此方法包裹。
next.config.mjsnext.config.ts| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| | | 兜底路由挂载的路径前缀 |
PersistenceAdapter
PersistenceAdapterPersistenceAdapter
PersistenceAdaptertypescript
interface PersistenceAdapter {
load(): Promise<string | null>
save(data: string): Promise<void>
}The built-in from provides a file-based adapter for local development.
filePersistence(path)@emulators/coretypescript
interface PersistenceAdapter {
load(): Promise<string | null>
save(data: string): Promise<void>
}@emulators/corefilePersistence(path)