---
title: "Next.js"
description: "广泛事件、结构化错误、排水管道、尾部采样、基于路由的服务、错误处理以及 Next.js 应用程序中的客户端日志记录。"
canonical_url: "https://evlog.zhcndoc.com/integrate/frameworks/nextjs"
---
# Next.js

> 广泛事件、结构化错误、排水管道、尾部采样、基于路由的服务、错误处理以及 Next.js 应用程序中的客户端日志记录。

evlog 通过一个 `createEvlog()` 工厂函数与 Next.js App Router 集成，提供 `withEvlog()` 处理器包装器、`useLogger()` 以及类型化导出。一个文件，零全局状态。

<prompt :actions="["copy","cursor","claude"]" description="在我的 Next.js 应用中设置 evlog" icon="i-simple-icons-nextdotjs">

在我的 Next.js 应用中设置 evlog，使用广泛事件和结构化错误。

- 安装 evlog：pnpm add evlog
- 创建 evlog.config.ts，其中包含 export default defineEvlog({ service: 'my-app' })；在其中设置采样、drain、enrich 和路由过滤器
- 创建 lib/evlog.ts，从 evlog.config.ts 导入配置，并使用 createEvlog(config) 导出 withEvlog、useLogger、createError
- 使用 withEvlog() 包装 API 路由处理器
- 在处理器内部使用 useLogger() 构建带有 log.set() 的广泛事件
- 使用 createError({ message, status, why, fix }) 抛出错误
- 广泛事件会在每个请求完成时自动发出

文档：[https://www.evlog.dev/integrate/frameworks/nextjs](https://www.evlog.dev/integrate/frameworks/nextjs)
适配器：[https://www.evlog.dev/integrate/adapters/overview](https://www.evlog.dev/integrate/adapters/overview)

</prompt>

## 快速开始

### 1. 安装

<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. 创建 evlog.config.ts

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

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

所有选项都位于此文件中：采样、drain、`enrich` 和路由过滤器。`npx evlog init` 会为你写入它。请参阅[配置](https://evlog.zhcndoc.com/reference/configuration)。

### 3. 创建你的 evlog 实例

```typescript [lib/evlog.ts]
import { createEvlog } from 'evlog/next'
import config from '../evlog.config'

export const { withEvlog, useLogger, log, createError } = createEvlog(config)
```

`createEvlog(config)` 会在首次导入 `lib/evlog.ts` 时启动日志记录器，使用 `withEvlog` 包装的路由会采用文件中的路由过滤器、drain、`enrich` 和 `keep`。如果还要排空请求外的 `log.*` 调用、报告未处理错误并应用文件中的 `environment`，请按照[从 evlog.config.ts 启动日志记录器](#start-the-logger-from-evlogconfigts)中的说明，在 `instrumentation.ts` 中从文件启动日志记录器。

### 4. 包装路由处理器

```typescript [app/api/hello/route.ts]
import { withEvlog, useLogger } from '@/lib/evlog'

export const GET = withEvlog(async () => {
  const log = useLogger()
  log.set({ action: 'hello' })
  return Response.json({ message: 'Hello!' })
})
```

## 仪表化

Next.js 支持在项目根目录下使用 [`instrumentation.ts`](https://nextjs.org/docs/app/guides/instrumentation) 文件来处理服务器启动钩子和错误上报。evlog 提供 `createInstrumentation()` 来集成这一模式。

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

这两个 API 用途不同，可以独立或一起使用：

- **createEvlog()**：通过 `withEvlog()` 实现每个请求的广泛事件
- **createInstrumentation()**：服务器启动（`register()`）+ 全局未处理错误上报（`onRequestError()`），适用于所有路由，包括 SSR 和 RSC
- 两者可以共存：`register()` 初始化并锁定日志记录器，因此 `createEvlog()` 会遵循其配置。每个可以拥有自己的 `drain`。

</callout>

### 1. 将仪表化与路由配置分离

将仅限 Node 的导入（`evlog/fs`、重量级适配器）排除在根目录的 `instrumentation.ts` 之外。使用带有选项对象的 `defineNodeInstrumentation`。evlog 仅在 Node.js 中加载 `createInstrumentation`，不会在你的文件中出现明显的 `import()`。

- 根 `instrumentation.ts` → `defineNodeInstrumentation({ service, ... })`
- `lib/evlog.ts` → `createEvlog()` 和用于 API 路由的仅限 Node 的 drain

```typescript [instrumentation.ts]
import { defineNodeInstrumentation } from 'evlog/next/instrumentation'

export const { register, onRequestError } = defineNodeInstrumentation({
  service: 'my-app',
  captureOutput: true,
})
```

```typescript [lib/evlog.ts]
import { createEvlog } from 'evlog/next'
import { createFsDrain } from 'evlog/fs'

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  drain: createFsDrain(),
})
```

### 2. 连接 instrumentation.ts

Next.js 会在 Node.js 和 Edge 运行时中同时评估 `instrumentation.ts`。`defineNodeInstrumentation` 会根据 `NEXT_RUNTIME === 'nodejs'` 进行判断，并在内部加载仅限 Node 的工厂。

### 自定义行为（evlog + 你的代码）

当你需要在 evlog 之外再执行额外的启动工作时，请传入一个 **加载器回调**：

```typescript [instrumentation.ts]
import { defineNodeInstrumentation } from 'evlog/next/instrumentation'

export const { register, onRequestError } = defineNodeInstrumentation(async () => {
  const { createInstrumentation } = await import('evlog/next/instrumentation/create')
  const { register: evlogRegister, onRequestError: evlogOnRequestError } = createInstrumentation({
    service: 'my-app',
    captureOutput: true,
  })

  return {
    async register() {
      await evlogRegister()
      // e.g. OpenTelemetry, feature flags, custom one-off init
    },
    onRequestError: evlogOnRequestError,
  }
})
```

保留 `lib/evlog.ts` 用于 `createEvlog()` 和仅 Node 的排水器。路由处理程序导入 `@/lib/evlog`。

Next.js 会自动调用这些导出：

- `register()`：服务器启动时运行一次。使用配置的排水器、采样和选项初始化 evlog 日志记录器。如果启用了 `captureOutput`，则 `stdout` 和 `stderr` 的写入会被捕获为结构化日志事件。
- `onRequestError()`：每个未处理的请求错误都会调用。发出包含错误消息、摘要、堆栈跟踪、请求路径/方法以及路由上下文（`routerKind`、`routePath`、`routeType`、`renderSource`）的结构化错误日志。

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

`captureOutput` 仅在 Node.js 运行时（`NEXT_RUNTIME === 'nodejs'`）中激活。它会修补 `process.stdout.write` 和 `process.stderr.write`，以发出结构化的 `log.info` / `log.error` 事件。当 `silent` 为 `false`（默认值）时，捕获的输出会以结构化终端输出的形式显示一次，并且不会重复原始写入。evlog 自身的输出也会经过捕获，因此 drain 每个广泛事件只会收到一次。设置 `silent: true` 可在保留 drain 传递的同时保留原始直通输出。已知的 Next.js Edge 打包器警告默认会被过滤，因此不会作为 evlog 错误重新发出。

</callout>

### 配置

`defineNodeInstrumentation()` 和 `createInstrumentation()` 接受全局日志记录器选项（`enabled`、`service`、`env`、`pretty`、`silent`、`sampling`、`stringify`、`drain`）以及：

<table>
<thead>
  <tr>
    <th>
      选项
    </th>
    
    <th>
      类型
    </th>
    
    <th>
      默认值
    </th>
    
    <th>
      描述
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        captureOutput
      </code>
    </td>
    
    <td>
      <code>
        boolean | CaptureOutputOptions
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      将 stdout/stderr 捕获为结构化日志事件
    </td>
  </tr>
</tbody>
</table>

`CaptureOutputOptions` 字段：

<table>
<thead>
  <tr>
    <th>
      字段
    </th>
    
    <th>
      类型
    </th>
    
    <th>
      默认值
    </th>
    
    <th>
      描述
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        stdout
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      捕获 stdout 写入
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        stderr
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      捕获 stderr 写入
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ignore
      </code>
    </td>
    
    <td>
      <code>
        (string | RegExp)[]
      </code>
    </td>
    
    <td>
      Next.js Edge 打包器警告
    </td>
    
    <td>
      跳过将匹配的块重新发作为日志事件
    </td>
  </tr>
</tbody>
</table>

```typescript [instrumentation.ts]
defineNodeInstrumentation({
  captureOutput: {
    stderr: true,
    ignore: [/my-noisy-dep/, 'benign warning'],
  },
})
```

## 生产配置

一个真实的 `lib/evlog.ts`，包含增强器、分批排水、尾部采样和基于路由的服务名称：

<code-collapse>

```typescript [lib/evlog.ts]
import type { DrainContext } from 'evlog'
import { createEvlog } from 'evlog/next'
import { createUserAgentEnricher, createRequestSizeEnricher } from 'evlog/enrichers'
import { createAxiomDrain } from 'evlog/axiom'
import { createDrainPipeline } from 'evlog/pipeline'

// 1. 增强器 - 为每个事件添加派生上下文
const enrichers = [createUserAgentEnricher(), createRequestSizeEnricher()]

// 2. 管道 - 在发送前批量处理事件
const pipeline = createDrainPipeline<DrainContext>({ batch: { size: 50, intervalMs: 5000 } })

// 3. 排水器 - 将批量事件发送到 Axiom
const drain = pipeline(createAxiomDrain({
  dataset: 'logs',
  apiKey: process.env.AXIOM_API_KEY!,
}))

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',

  // 4. 头部采样 - 保留 10% 的 info 日志
  sampling: {
    rates: { info: 10 },
    keep: [
      { status: 400 },              // 始终保留 4xx/5xx 错误
      { duration: 1000 },           // 始终保留缓慢请求
      { path: '/api/critical/**' }, // 始终保留关键路径
    ],
  },

  // 5. 基于路由的服务名称
  routes: {
    '/api/auth/**': { service: 'auth-service' },
    '/api/payment/**': { service: 'payment-service' },
    '/api/booking/**': { service: 'booking-service' },
  },

  // 6. 自定义尾部采样 - 业务逻辑
  keep: (ctx) => {
    const user = ctx.context.user as { premium?: boolean } | undefined
    if (user?.premium) ctx.shouldKeep = true
  },

  // 7. 使用增强器为每个事件添加用户代理、请求大小和部署信息
  enrich: (ctx) => {
    for (const enricher of enrichers) enricher(ctx)
    ctx.event.deploymentId = process.env.VERCEL_DEPLOYMENT_ID
    ctx.event.region = process.env.VERCEL_REGION
  },

  drain,
})
```

</code-collapse>

<callout color="neutral" icon="i-lucide-cloud">

在 Vercel 上，`withEvlog` 会使用 Next 的 `after()` 注册排水工作；使用管道时，它还会注册 `drain.settled()`：该函数会一直保持活动状态，直到缓冲批次传送完成，或者在重试次数耗尽后被丢弃。`batch.intervalMs` 会限制首次传送尝试开始的时间；重试退避可能会使生命周期超过该时间。如果你的摄取端点响应速度太慢，达到默认的 5 秒请求超时，请提高超时时间（`createAxiomDrain({ timeout: 15000 })`）：实际已经到达目标的超时请求会被重试，而非幂等的摄取 API 会将其计为两次。

</callout>

## 广泛事件

通过处理器逐步构建上下文。一次请求 = 一个广泛事件：

```typescript [app/api/checkout/route.ts]
import { withEvlog, useLogger } from '@/lib/evlog'

export const POST = withEvlog(async (request: Request) => {
  const log = useLogger()
  const body = await request.json()

  // 阶段 1：用户上下文
  log.set({
    user: { id: body.userId, plan: 'enterprise' },
  })

  // 阶段 2：购物车上下文
  log.set({
    cart: { items: body.items.length, total: body.total, currency: 'USD' },
  })

  // 阶段 3：支付上下文
  const payment = await processPayment(body)
  log.set({
    payment: { method: payment.method, cardLast4: payment.last4 },
  })

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

所有字段都会合并为一个宽泛的事件，并在处理程序完成时发出（或者在流式响应体结束时发出，因此会包含 AI SDK 元数据）：

```bash [输出（美化）]
10:23:45.612 INFO [my-app] POST /api/checkout 200 in 145ms
  ├─ user: id=usr_123 plan=enterprise
  ├─ cart: items=3 total=14999 currency=USD
  ├─ payment: method=card cardLast4=4242
  └─ requestId: a1b2c3d4-...
```

## 后台工作 (`log.fork`)

在 `withEvlog` 内部，`useLogger()` 会返回一个带有 **fork** 的日志记录器，用于创建子广泛事件。请参阅广泛事件中的[发出后](https://evlog.zhcndoc.com/learn/wide-events#after-emit-sealing-and-background-work)。

```typescript [app/api/orders/route.ts]
import { withEvlog, useLogger } from '@/lib/evlog'

export const POST = withEvlog(async () => {
  const log = useLogger()
  log.fork!('enqueue', async () => {
    const child = useLogger()
    child.set({ job: 'queued' })
  })
  return Response.json({ ok: true })
})
```

## 错误处理

使用 `createError` 抛出带有 `why`、`fix` 和 `link` 字段的结构化错误，以帮助开发人员在日志和 API 响应中调试：

<code-collapse>

```typescript [app/api/payment/process/route.ts]
import { withEvlog, useLogger, createError } from '@/lib/evlog'

export const POST = withEvlog(async (request: Request) => {
  const log = useLogger()
  const body = await request.json()

  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',
    })
  }

  const result = await chargeCard(body)

  if (!result.success) {
    log.error(new Error(`Payment declined: ${result.reason}`))
    throw createError({
      status: 402,
      message: 'Payment declined',
      why: `Card declined by issuer: ${result.reason}`,
      fix: 'Try a different payment method or contact your bank',
    })
  }

  return Response.json({ success: true })
})
```

</code-collapse>

`withEvlog()` 会捕获 `EvlogError` 并返回结构化的 JSON 响应（类似于 Nitro 对 Nuxt 的处理）：

```json [响应（402）]
{
  "name": "EvlogError",
  "message": "Payment declined",
  "status": 402,
  "data": {
    "why": "Card declined by issuer: insufficient_funds",
    "fix": "Try a different payment method or contact your bank"
  }
}
```

在终端中，错误会显示在广泛事件内部：先显示错误块，然后显示请求上下文。颜色和树形连接符会在终端中渲染；下面的示例为便于阅读省略了 ANSI 控制码。

```bash [Terminal output]
ERROR [app] POST /api/payment/process 402 in 12ms
  ├─ error: Payment declined
  │     at app/api/payment/process/route.ts:336
  │   ❯ 336 ┃ throw createError({ message: 'Payment declined', ... })
  │     Why: Card declined by issuer: insufficient_funds
  │     Fix: Try a different payment method or contact your bank
  │     stack (3 frames hidden in node_modules)
  └─ payment: amount=4999
```

### 在客户端解析错误

使用 `parseError` 从任意错误中提取结构化字段，无论是 fetch 响应、`EvlogError` 还是普通的 `Error` 对象：

```tsx [app/components/PaymentForm.tsx]
'use client'
import { parseError } from 'evlog'

async function handleSubmit(formData: FormData) {
  try {
    const res = await fetch('/api/payment/process', {
      method: 'POST',
      body: JSON.stringify({ amount: Number(formData.get('amount')) }),
    })
    if (!res.ok) throw { data: await res.json(), status: res.status }
  } catch (error) {
    const { message, status, why, fix, link } = parseError(error)
    // message: "支付被拒绝"
    // why: "发卡行拒绝卡片：资金不足"
    // fix: "尝试其他支付方式或联系您的银行"
  }
}
```

`parseError` 会将任意错误格式规范化为扁平的 `{ message, status, why?, fix?, link? }` 对象，这样你的 UI 代码就无需处理嵌套的 `data.data` 或检查不同的错误格式。

## 配置

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

查看 [配置参考](https://evlog.zhcndoc.com/reference/configuration) 以获取共享选项的完整列表（`enabled`、`pretty`、`silent`、`sampling`、中间件选项等）。

</callout>

`createEvlog()` 工厂接受以下选项：

<table>
<thead>
  <tr>
    <th>
      选项
    </th>
    
    <th>
      类型
    </th>
    
    <th>
      默认值
    </th>
    
    <th>
      说明
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        service
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'app'
      </code>
    </td>
    
    <td>
      日志中显示的服务名称
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        environment
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      自动检测
    </td>
    
    <td>
      环境名称
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        include
      </code>
    </td>
    
    <td>
      <code>
        string[]
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      要记录的路径模式
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        exclude
      </code>
    </td>
    
    <td>
      <code>
        string[]
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      要排除的路径模式
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        routes
      </code>
    </td>
    
    <td>
      <code>
        Record<string, RouteConfig>
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      路由特定的服务配置
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sampling.rates
      </code>
    </td>
    
    <td>
      <code>
        object
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      每种日志级别的头部采样率
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sampling.keep
      </code>
    </td>
    
    <td>
      <code>
        array
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      尾部采样条件
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        keep
      </code>
    </td>
    
    <td>
      <code>
        (ctx: TailSamplingContext) => void
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      自定义尾部采样回调
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        drain
      </code>
    </td>
    
    <td>
      <code>
        DrainFunction
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      用于外部服务的排水适配器
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        enrich
      </code>
    </td>
    
    <td>
      <code>
        (ctx: EnrichContext) => void
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      事件增强回调
    </td>
  </tr>
</tbody>
</table>

### 从 evlog.config.ts 启动日志记录器

在 `lib/evlog.ts` 中读取该文件，同时配置 instrumentation 和 `createEvlog()`：

```typescript [lib/evlog.ts]
import { toLoggerConfig } from 'evlog'
import { createEvlog } from 'evlog/next'
import { createInstrumentation } from 'evlog/next/instrumentation/create'
import config from '../evlog.config'

export const { register, onRequestError } = createInstrumentation({
  ...toLoggerConfig(config),
  captureOutput: true,
})

export const { withEvlog, useLogger, log, createError } = createEvlog(config)
```

```typescript [instrumentation.ts]
import { defineNodeInstrumentation } from 'evlog/next/instrumentation'

export const { register, onRequestError } = defineNodeInstrumentation(() => import('./lib/evlog'))
```

`defineNodeInstrumentation` 只会在 Node.js 上加载 `lib/evlog.ts`，因此配置导入的 drain 不会进入 Edge bundle。`register()` 会在任何路由运行前从文件启动日志记录器，`createEvlog()` 会从文件中读取路由过滤器、`enrich` 和 `keep`。[`evlog init`](https://evlog.zhcndoc.com/cli/init#nextjs) 会为你写入这两个文件。

## 尾部采样

结合基于规则的尾部采样和自定义尾部采样，确保记录重要内容，即使头部采样丢弃了大多数日志：

```typescript [lib/evlog.ts]
export const { withEvlog, useLogger } = createEvlog({
  service: 'my-app',
  sampling: {
    rates: { info: 10 }, // 仅保留 10% 的 info 日志
    keep: [
      { status: 400 },              // 始终保留 4xx/5xx
      { duration: 1000 },           // 始终保留缓慢请求
      { path: '/api/critical/**' }, // 始终保留关键路径
    ],
  },
  // 自定义：始终保留 premium 用户请求
  keep: (ctx) => {
    const user = ctx.context.user as { premium?: boolean } | undefined
    if (user?.premium) ctx.shouldKeep = true
  },
})
```

`keep` 规则采用 OR 逻辑：任何匹配都会强制事件通过，无论头部采样如何。

## 中间件

设置 `x-request-id` 和 `x-evlog-start` 标头，以便 `withEvlog()` 可以在中间件 -> 处理器链中关联计时：

```typescript [proxy.ts]
import { evlogMiddleware } from 'evlog/next'

export const proxy = evlogMiddleware()

export const config = {
  matcher: ['/api/:path*'],
}
```

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

旧版本的 Next.js 使用 `middleware.ts` 而不是 `proxy.ts`。evlog 中间件与两者都兼容，因此无论哪种情况都应从 `evlog/next` 导入。

</callout>

## 服务器行为

`withEvlog()` 也适用于服务器操作。包装你的行为以获得完整的请求范围日志记录：

```typescript [app/actions/checkout.ts]
'use server'
import { withEvlog, useLogger } from '@/lib/evlog'

export const checkout = withEvlog(async (formData: FormData) => {
  const log = useLogger()
  log.set({ action: 'checkout', cartId: formData.get('cartId') })
  // ...
})
```

## 客户端提供者

使用 `EvlogProvider` 包装根布局，以启用客户端日志记录及其排水器：

```tsx [app/layout.tsx]
import { EvlogProvider } from 'evlog/next/client'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <EvlogProvider service="my-app" transport={{ enabled: true }}>
          {children}
        </EvlogProvider>
      </body>
    </html>
  )
}
```

## 客户端日志记录

在任何客户端组件中使用 `log`。身份信息会保留在所有日志中并传输到服务器：

```tsx [app/components/Dashboard.tsx]
'use client'
import { log, setIdentity, clearIdentity } from 'evlog/next/client'

export function Dashboard({ user }: { user: { id: string } }) {
  // 一次性设置身份 - 后续所有日志均包含该身份
  useEffect(() => {
    setIdentity({ userId: user.id })
    return () => clearIdentity()
  }, [user.id])

  return (
    <button onClick={() => log.info({ action: 'export_clicked', format: 'csv' })}>
      导出
    </button>
  )
}
```

## HTTP 排水

对于高级用例，可直接从浏览器向自定义端点发送结构化的 `DrainContext` 事件：

```typescript [lib/http-drain.ts]
import { createHttpLogDrain } from 'evlog/http'

const drain = createHttpLogDrain({
  drain: { endpoint: '/api/evlog/http-ingest' },
  pipeline: { batch: { size: 10, intervalMs: 5000 } },
})

drain(drainEvent)
await drain.flush()
```

服务器端点接收批量事件：

```typescript [app/api/evlog/http-ingest/route.ts]
export async function POST(request: Request) {
  const events = await request.json()
  // 转发到你的排水管道、Axiom 等
  return new Response(null, { status: 204 })
}
```

## 本地运行

```bash [Terminal]
git clone https://github.com/evloghq/evlog.git
cd evlog/examples/nextjs
pnpm install
pnpm run dev
```

打开 [http://localhost:3000](http://localhost:3000) 探索示例。

<card-group>
<card icon="i-simple-icons-github" title="源代码" to="https://github.com/evloghq/evlog/tree/main/examples/nextjs">

在 GitHub 上浏览完整的 Next.js 示例源代码。

</card>
</card-group>

## 后续步骤

深入集成 **Next.js**：

- [宽事件](https://evlog.zhcndoc.com/learn/wide-events): 使用上下文分层设计全面的事件
- [适配器](https://evlog.zhcndoc.com/integrate/adapters/overview): 将日志发送到 Axiom、Sentry、PostHog 等
- [采样](https://evlog.zhcndoc.com/learn/sampling): 使用头部采样和尾部采样控制日志量
- [结构化错误](https://evlog.zhcndoc.com/learn/structured-errors): 抛出包含 `why`、`fix` 和 `link` 字段的错误。

---

- [源代码](https://github.com/evloghq/evlog/tree/main/examples/nextjs)


## Sitemap

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