mcpField
N
next

next

github.com/vercel-labs开发2.0k187 次安装1 天前更新
代码仓库

关于 next

@emulators/adapter-next 将 API 模拟器嵌入 Next.js App Router 应用,并通过同源路径处理请求,适用于需要在预览部署中测试 OAuth 的开发者。它支持多个模拟服务及可选状态持久化。

主要功能

  1. 01嵌入 Next.js App Router
  2. 02同源路径路由多个模拟器
  3. 03支持可选持久化适配器
  4. 04自动保存变更请求后的状态
  5. 05配置 serverless 字体追踪

适用场景

  1. 01在 Vercel 预览部署中测试 OAuth 回调
  2. 02将多个 API 模拟器挂载到 Next.js 路由
  3. 03通过自定义适配器跨冷启动保存状态
  4. 04本地开发时将状态写入 JSON 文件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/vercel-labs/emulate --skill next

安装后在 Claude Code 中输入/next确认已加载。

详细介绍

Next.js 集成

@emulators/adapter-next 包可将模拟器直接嵌入 Next.js App Router 应用,并使其运行在同一来源上。这对于 Vercel 预览部署尤其有用,因为每次部署都会更改 OAuth 回调 URL。

安装

npm install @emulators/adapter-next @emulators/github @emulators/google

只安装所需的模拟器。每个 @emulators/* 包都独立发布,从而保持无服务器打包体积较小。

Route Handler

创建一个捕获所有请求的路由来处理模拟器流量:

// 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, OPTIONS } = 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' }],
      },
    },
  },
})

这会创建以下路由:

  • /emulate/github/** 提供 GitHub 模拟器
  • /emulate/google/** 提供 Google 模拟器

GitHub 安装令牌元数据可在服务端通过 /emulate/github/_emulate/installation-tokens 获取。

Auth.js / NextAuth 配置

将提供程序指向同一来源上的模拟器路径:

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_apps。未配置任何条目时,模拟器会跳过对 client_id、client_secret 和 redirect_uri 的验证。

无服务器环境的字体追踪

模拟器 UI 页面使用内置字体。请包装 Next.js 配置,将这些字体包含在无服务器追踪中:

// next.config.mjs
import { withEmulate } from '@emulators/adapter-next'

export default withEmulate({
  // your normal Next.js config
})

如果将捕获所有请求的路由挂载到自定义路径,请传入匹配的前缀:

export default withEmulate(nextConfig, { routePrefix: '/api/emulate' })

持久化

默认情况下,模拟器状态保存在内存中,并会在每次冷启动时重置。若要在重启后保留状态,请传入 persistence 适配器。

自定义适配器(Vercel KV、Redis 等)
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,
})
文件持久化(本地开发)

用于本地开发时,@emulators/core 提供基于文件的适配器:

import { filePersistence } from '@emulators/core'

// persists to a JSON file
persistence: filePersistence('.emulate/state.json'),

GitHub App 种子数据可以省略 private_key。请保留处理程序并调用仅限服务端使用的 generatedSecrets();显式密钥不会被排除在外。请妥善保管持久化快照,并以原子方式实现 initialize。

持久化工作原理
  • 冷启动:适配器从持久化适配器加载状态。如果找到状态,则恢复完整的 Store 和令牌映射(跳过种子初始化)。如果未找到,则根据配置填充种子数据并保存初始状态。
  • 变更请求后(POST、PUT、PATCH、DELETE):保存状态。保存操作通过内部队列串行执行,以防止竞态条件。
  • 未配置持久化:退回到纯内存模式。每次冷启动时都会重新初始化种子数据。

工作原理

  1. 收到请求:/emulate/github/login/oauth/authorize?client_id=...
  2. 解析:service = github,rest = /login/oauth/authorize
  3. 移除前缀:创建一个路径已移除前缀的新 Request,并将其转发给 GitHub 服务应用
  4. 重写响应:为 HTML action 和 href 属性、CSS url() 字体引用以及 Location 标头添加服务前缀
  5. 持久化:变更请求后,通过持久化适配器保存状态

限制

  • 需要使用 Node.js 运行时(而非 Edge),因为模拟器使用 crypto.randomBytes
  • 并发变更采用最后写入胜出语义。生成的身份要求 initialize 在多次冷启动之间以原子方式选择初始快照。

配置参考

createEmulateHandler(config)
字段类型描述
servicesRecord<string, EmulatorEntry>将服务名称映射到模拟器配置
persistence?PersistenceAdapter可选的持久化适配器,用于在冷启动之间保存状态

每个 EmulatorEntry:

字段类型描述
emulatorEmulatorModule模拟器包(例如 import * as github from '@emulators/github')
seed?Record<string, unknown>符合该服务配置架构的种子数据
withEmulate(nextConfig, options?)

包装 Next.js 配置,将模拟器字体文件包含在无服务器输出追踪中。在 next.config.mjs 或 next.config.ts 中,对导出的配置进行包装。

选项类型默认值描述
routePrefixstring"/emulate"捕获所有请求的路由所挂载的路径前缀
PersistenceAdapter
interface PersistenceAdapter {
  load(): Promise<string | null>
  save(data: string): Promise<void>
  initialize?(data: string): Promise<string>
}

initialize 必须以原子方式创建初始值,或返回另一个实例先创建的值。请使用比较并交换语义实现,例如 Redis SET NX。@emulators/core 提供的内置 filePersistence(path) 可用于本地开发。

自定义模拟器

适配器接受由 emulate 中的 defineEmulator 创建的定义,并将其放在 services[name].emulator 中。CLI 和测试可复用该定义;自定义检查器为可选项。相对于根目录的重定向位置会保留在服务挂载路径下,而自定义 HTML 正文则会原样传递。在测试清理阶段,等待返回的处理程序的 close() 完成。对于部署的定义,请将 emulate 安装为运行时依赖项。有关种子、快照、持久化和生命周期规则,请参阅 https://emulate.dev/docs/custom-emulators。

评论

支持 Markdown