---
title: "Nuxt"
description: "Add the Nuxt module and every server route emits one wide event. `useLogger(event)` and `parseError` are auto-imported, `createError` comes from `evlog`."
canonical_url: "https://www.evlog.dev/integrate/frameworks/nuxt"
---
# Nuxt

> Add the Nuxt module and every server route emits one wide event. `useLogger(event)` and `parseError` are auto-imported, `createError` comes from `evlog`.

evlog provides a first-class Nuxt module with auto-imported `useLogger` and `parseError`. Add it to your config and start logging with zero boilerplate.

<prompt :actions="["copy","cursor","claude"]" description="Set up evlog in my Nuxt app" icon="i-simple-icons-nuxtdotjs">

Set up evlog in my Nuxt app with wide events and structured errors.

- Install evlog: pnpm add evlog
- Add 'evlog/nuxt' to modules in nuxt.config.ts
- Create evlog.config.ts at the app root with export default defineEvlog({ service: 'my-app' }); the module loads it
- useLogger and parseError are auto-imported, import createError from 'evlog'
- Create a server/api route using useLogger(event) and log.set() to build a wide event
- Throw errors with createError({ message, status, why, fix }), imported from 'evlog'
- Wide events are auto-emitted when each request completes

Docs: [https://www.evlog.dev/integrate/frameworks/nuxt](https://www.evlog.dev/integrate/frameworks/nuxt)
Adapters: [https://www.evlog.dev/integrate/adapters/overview](https://www.evlog.dev/integrate/adapters/overview)

</prompt>

## Quick Start

### 1. Install

<code-group>

```bash [pnpm]
pnpm add evlog
```

```bash [bun]
bun add evlog
```

```bash [yarn]
yarn add evlog
```

```bash [npm]
npm install evlog
```

</code-group>

### 2. Create evlog.config.ts

```typescript [evlog.config.ts]
import { defineEvlog } from 'evlog'

export default defineEvlog({
  service: 'my-app',
})
```

Every option lives in this file: sampling, drain, `enrich`, route filters. `npx evlog init` writes it for you. See [Configuration](https://www.evlog.dev/reference/configuration).

### 3. Add the module

```typescript [nuxt.config.ts]
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
})
```

That's it. The module finds `evlog.config.ts` and loads it, with nothing to import. `useLogger` and `parseError` are auto-imported. `createError` is imported from `evlog`.

## Wide Events

Build up context progressively throughout a request with `useLogger(event)`. evlog emits a single wide event when the request completes.

```typescript [server/api/checkout.post.ts]
export default defineEventHandler(async (event) => {
  const log = useLogger(event)
  const body = await readBody(event)

  log.set({ user: { id: body.userId, plan: 'enterprise' } })

  const cart = await db.findCart(body.cartId)
  log.set({ cart: { items: cart.items.length, total: cart.total } })

  const payment = await processPayment(cart)
  log.set({ payment: { method: payment.method, cardLast4: payment.last4 } })

  return { success: true, orderId: payment.orderId }
})
```

One request, one log line with all context:

```bash [Terminal output]
10:23:45 INFO [my-app] POST /api/checkout 200 in 145ms
  ├─ user: id=usr_123 plan=enterprise
  ├─ cart: items=3 total=14999
  ├─ payment: method=card cardLast4=4242
  └─ requestId: a1b2c3d4-...
```

## Error Handling

`createError` produces structured errors with `why`, `fix`, and `link` fields that help both humans and AI agents understand what went wrong. Import it from `evlog`: a bare `createError` in a server route is h3's, and h3 drops any field it does not know.

```typescript [server/api/payment/process.post.ts]
import { createError } from 'evlog'

export default defineEventHandler(async (event) => {
  const log = useLogger(event)
  const body = await readBody(event)

  log.set({ payment: { amount: body.amount } })

  if (body.amount <= 0) {
    throw createError({
      status: 400,
      message: 'Invalid payment amount',
      why: 'The amount must be a positive number',
      fix: 'Pass a positive integer in cents (e.g. 4999 for $49.99)',
      link: 'https://docs.example.com/api/payments#amount',
    })
  }

  return { success: true }
})
```

<callout color="info" icon="i-lucide-info">

Nuxt's error handler automatically catches `EvlogError` and returns a structured JSON response with `why`, `fix`, and `link` fields. Errors thrown with h3's `createError` keep their `data` payload, as they do without the module.

</callout>

## Configuration

<callout color="info" icon="i-lucide-book-open">

See the [Configuration reference](https://www.evlog.dev/reference/configuration) for the full list of shared options (`enabled`, `pretty`, `silent`, `sampling`, middleware options, etc.).

</callout>

Put options in `evlog.config.ts`. The `evlog` key in `nuxt.config.ts` takes the same options and overrides the file, merged key by key. The file's `drain`, `enrich` and `keep` are never overridden: they run alongside the `evlog:*` hooks, so an event reaches both.

The browser logger takes `enabled`, `pretty` and `minLevel` from the file unless the `evlog` key or `NUXT_PUBLIC_EVLOG_*` sets them. `console` and `transport` come from `nuxt.config.ts` only.

The table below uses the file's keys; in `evlog.config.ts` you can write `service` and `environment` at the top level instead of under `env`.

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        enabled
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Globally enable/disable all logging. When <code>
        false
      </code>
      
      , all operations become no-ops
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        console
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Enable/disable browser console output
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        env.service
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'app'
      </code>
    </td>
    
    <td>
      Service name shown in logs
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        env.environment
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Auto-detected
    </td>
    
    <td>
      Environment name
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        include
      </code>
    </td>
    
    <td>
      <code>
        string[]
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      Route patterns to log. Supports glob (<code>
        /api/**
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        exclude
      </code>
    </td>
    
    <td>
      <code>
        string[]
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      Route patterns to exclude. Exclusions take precedence
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        routes
      </code>
    </td>
    
    <td>
      <code>
        Record<string, RouteConfig>
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      Route-specific service configuration
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pretty
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
      
       in dev
    </td>
    
    <td>
      Pretty print with tree formatting
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        dev
      </code>
    </td>
    
    <td>
      <code>
        'evlog' | 'nitro' | 'both' | object
      </code>
    </td>
    
    <td>
      <code>
        'evlog'
      </code>
      
       in pretty dev
    </td>
    
    <td>
      Dev terminal presets or <code>
        { frameworkOverlay, prettyError }
      </code>
      
       — see <a href="https://www.evlog.dev/learn/structured-errors#development-terminal-output">
        Configuration — Dev terminal output
      </a>
    </td>
  </tr>
</tbody>
</table>

<callout color="info" icon="i-lucide-terminal">

**Dev terminal presets:** `'evlog'` (default) gives one clean signal, evlog-only stack. `'nitro'` gives the wide event context + Nitro Youch stack (evlog prints Why/Fix only). `'both'` gives the full evlog block and the Nitro overlay. With `pretty: false`, set `dev: { frameworkOverlay: false }` to suppress Nitro while logging JSON.

</callout>

| `silent` | `boolean` | `false` | Suppress console output. Events are still built, sampled, and drained. Use for stdout-based platforms |
| `sampling.rates` | `object` | `undefined` | Head sampling rates per log level (0-100%) |
| `sampling.keep` | `array` | `undefined` | Tail sampling conditions to force-keep logs |
| `transport.enabled` | `boolean` | `false` | Enable client-to-server log transport |
| `transport.endpoint` | `string` | `'/api/_evlog/ingest'` | Transport endpoint |

## Route Filtering

Use `include` and `exclude` to control which routes are logged:

```typescript [evlog.config.ts]
export default defineEvlog({
  include: ['/api/**', '/auth/**'],
  exclude: [
    '/api/_nuxt_icon/**',
    '/api/_content/**',
    '/api/health',
  ],
})
```

<callout color="warning" icon="i-lucide-alert-triangle">

**Exclusions take precedence.** If a path matches both `include` and `exclude`, it will be excluded.

</callout>

### Route-Based Service Names

Assign different service names to different route groups:

```typescript [evlog.config.ts]
export default defineEvlog({
  service: 'default-service',
  routes: {
    '/api/auth/**': { service: 'auth-service' },
    '/api/payment/**': { service: 'payment-service' },
    '/api/booking/**': { service: 'booking-service' },
  },
})
```

## Drain & Enrichers

Set `drain` and `enrich` in `evlog.config.ts`:

```typescript [evlog.config.ts]
import { defineEvlog } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'

export default defineEvlog({
  service: 'my-app',
  drain: createAxiomDrain(),
  enrich: (ctx) => {
    ctx.event.region = process.env.FLY_REGION
  },
})
```

A buffered drain from the file, such as one built with `createDrainPipeline`, is flushed when the server closes. Nitro plugins hooking `evlog:drain` and `evlog:enrich`, as below, still work and run alongside the file: both receive every event.

### Drain Plugin

```typescript [server/plugins/evlog-drain.ts]
import type { DrainContext } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
import { createDrainPipeline } from 'evlog/pipeline'

const pipeline = createDrainPipeline<DrainContext>({
  batch: { size: 50, intervalMs: 5000 },
  retry: { maxAttempts: 3 },
})
const drain = pipeline(createAxiomDrain())

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', drain)
})
```

### Enricher Plugin

```typescript [server/plugins/evlog-enrich.ts]
import {
  createUserAgentEnricher,
  createGeoEnricher,
  createRequestSizeEnricher,
  createTraceContextEnricher,
} from 'evlog/enrichers'

const enrichers = [
  createUserAgentEnricher(),
  createGeoEnricher(),
  createRequestSizeEnricher(),
  createTraceContextEnricher(),
]

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:enrich', (ctx) => {
    for (const enricher of enrichers) enricher(ctx)
  })
})
```

<callout color="neutral" icon="i-lucide-arrow-right">

See the [Adapters](https://www.evlog.dev/integrate/adapters/overview) and [Enrichers](https://www.evlog.dev/use-cases/enrichers) docs for the full list of available drains and enrichers.

</callout>

## Sampling

### Head Sampling

Randomly keep a percentage of logs per level. Runs before the request completes.

```typescript [nuxt.config.ts]
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    sampling: {
      rates: {
        info: 10,
        warn: 50,
        debug: 5,
        error: 100,
      },
    },
  },
})
```

Each level is a percentage from 0 to 100. Levels you don't configure default to 100% (keep everything). Error defaults to 100% even when other levels are configured.

### Tail Sampling

Evaluate after the request completes and force-keep logs that match specific conditions, regardless of head sampling.

```typescript [nuxt.config.ts]
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    sampling: {
      rates: { info: 10 },
      keep: [
        { duration: 1000 },
        { status: 400 },
        { path: '/api/critical/**' },
      ],
    },
  },
})
```

### Custom Tail Sampling

For conditions beyond status, duration, and path, use the `evlog:emit:keep` hook:

```typescript [server/plugins/evlog-sampling.ts]
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:emit:keep', (ctx) => {
    const user = ctx.context.user as { premium?: boolean } | undefined
    if (user?.premium) {
      ctx.shouldKeep = true
    }
  })
})
```

<callout color="info" icon="i-lucide-info">

Errors are always kept by default. You have to explicitly set `error: 0` to drop them.

</callout>

## Client Transport

Send browser logs to your server for processing and draining alongside server-side events.

```typescript [nuxt.config.ts]
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    transport: {
      enabled: true,
      endpoint: '/api/_evlog/ingest',
    },
  },
})
```

### How It Works

1. Client calls `log.info({ action: 'click', button: 'submit' })`
2. Log is sent to `/api/_evlog/ingest` via POST
3. Server enriches with environment context
4. `evlog:drain` hook is called with `source: 'client'`
5. External services receive the log

### Client Identity

Attach user context to every client log with `setIdentity`:

```typescript [Nuxt (auto-imported)]
// After login
setIdentity({ userId: 'usr_123', orgId: 'org_456' })

log.info({ action: 'checkout' })
// -> { userId: 'usr_123', orgId: 'org_456', action: 'checkout', ... }

// After logout
clearIdentity()
```

### Syncing Identity with Auth

Use a route middleware to keep identity in sync with your auth state:

```typescript [middleware/identity.global.ts]
export default defineNuxtRouteMiddleware(() => {
  const { user } = useAuth()

  if (user.value) {
    setIdentity({ userId: user.value.id, email: user.value.email })
  } else {
    clearIdentity()
  }
})
```

## Production Tips

Use Nuxt's `$production` override to keep full logging in development while sampling and disabling console output in production:

```typescript [nuxt.config.ts]
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    env: { service: 'my-app' },
  },
  $production: {
    evlog: {
      console: false,
      sampling: {
        rates: { info: 10, warn: 50, debug: 0 },
        keep: [{ duration: 1000 }, { status: 400 }],
      },
    },
  },
})
```

## Next Steps

Deepen your **Nuxt** integration:

- [Wide Events](https://www.evlog.dev/learn/wide-events): Design comprehensive events with context layering
- [Adapters](https://www.evlog.dev/integrate/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more
- [Sampling](https://www.evlog.dev/learn/sampling): Control log volume with head and tail sampling
- [Structured Errors](https://www.evlog.dev/learn/structured-errors): Throw errors with `why`, `fix`, and `link` fields


## Sitemap

See the full [sitemap](https://www.evlog.dev/sitemap.md) for all pages.
