---
title: "Nuxt Integration"
description: "Local drop-in replacement services for CI and no-network sandboxes. Fully stateful, production-fidelity API emulation."
canonical_url: "https://emulate.dev/docs/nuxt"
---
# Nuxt Integration

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

```bash
npm install @emulators/adapter-nuxt @emulators/github @emulators/google
```

Only install the emulators you need. Each `@emulators/*` package is published independently, keeping your server bundles small.

## Server Route

Create a named catch-all route that serves emulator traffic:

```typescript
// 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 App Private Keys

GitHub App seeds may omit `private_key`; retain the handler and call server-only `generatedSecrets()`. Keep persisted generated keys private and implement atomic `initialize`.

Minted GitHub App installation-token metadata is available 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:

```typescript
// 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:

```typescript
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

```typescript
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:

```typescript
import { filePersistence } from '@emulators/core'

persistence: filePersistence('.emulate/state.json'),
```

Generated identities require atomic create-or-read `initialize`; see `@emulators/core`.

### How It 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.

## Custom Mounts

The adapter reads the `path` param from `server/routes/emulate/[...path].ts`. If you use a different catch-all name, pass it as the second argument:

```typescript
export default defineEventHandler(createEmulateHandler(config, { param: 'slug' }))
```

If the mount path cannot be detected from the URL, pass `routePrefix`:

```typescript
export default defineEventHandler(createEmulateHandler(config, { routePrefix: '/api/emulate' }))
```

## How It Works

1. **Incoming request**: `/emulate/github/login/oauth/authorize?client_id=...`
2. **Parse**: service = `github`, rest = `/login/oauth/authorize`
3. **Strip prefix**: A new `Request` is created with the stripped path and forwarded to the GitHub service app
4. **Rewrite response**: HTML `action` and `href` attributes, CSS `url()` font references, and `Location` headers get the service prefix prepended
5. **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 use `initialize` to select the initial snapshot atomically across cold starts.

## Custom emulators

Custom definitions created with `defineEmulator` from `emulate` work in the CLI, in-process tests, and both framework adapters. The same definition provides typed state, HTTP handlers, reset, versioned snapshots, and optional inspection/persistence. Root-relative redirect locations stay under the mounted service path, while custom HTML bodies pass through unchanged. See [Custom emulators](/docs/custom-emulators) for the quickstart, API reference, and complete lifecycle semantics.
