Nuxt 3はSSR(サーバーサイドレンダリング)とCSR(クライアントサイドレンダリング)が混在するフルスタックフレームワークです。サーバー側のNitroとブラウザ側のVueアプリが連携して動作するため、障害発生時にどちらが原因なのかを特定するのが困難になりがちです。
OpenTelemetryは、この課題を解決するための業界標準の可観測性フレームワークです。CNCF(Cloud Native Computing Foundation)が管理するオープンソースプロジェクトで、ベンダーに依存しない統一的なテレメトリ(トレース・メトリクス・ログ)の収集・送信を実現します。
ただし2026年2月時点で、NuxtにはOpenTelemetryのビルトインサポートが存在しません。Next.jsが instrumentation.ts によるネイティブ対応を提供しているのとは対照的に、Nuxtではコミュニティ製モジュールや手動設定で補う必要があります。
本記事では、Nuxt 3にOpenTelemetryを組み込むための選択肢と具体的な手順を、コード例を交えながら体系的に整理します。
OpenTelemetryの3つの柱
OpenTelemetryが収集するテレメトリデータは、3種類に大別されます。
| データ種別 | 役割 | Nuxtでの活用例 |
|---|---|---|
| トレース(Traces) | リクエストの処理経路と所要時間を記録 | APIルートの応答遅延の特定、SSR描画のボトルネック検出 |
| メトリクス(Metrics) | 数値の集計データを定期的に収集 | リクエスト数、エラー率、Core Web Vitals(LCP・CLS・INP) |
| ログ(Logs) | イベント発生時のテキスト記録 | エラー詳細、デバッグ情報、ユーザー操作の記録 |
トレースは「スパン(Span)」と呼ばれる個々の処理単位で構成されます。1つのHTTPリクエストが「Nitroのルーティング → APIハンドラの実行 → データベースクエリ」と処理される場合、各段階がスパンとして記録され、処理全体を「トレース」として可視化できます。
Nuxt公式のOpenTelemetry対応状況(2026年2月時点)
Nuxtフレームワーク本体にはOpenTelemetryの統合機能がありません。この機能要望はGitHub Issue #24002 として2023年10月に起票され、p2-nice-to-have(優先度:中)のラベルが付いた状態でオープンのままです。
Vercel によるNuxtLabs買収の影響
2025年7月、VercelがNuxtLabsを買収しました(出典: Vercel Blog)。NuxtとNitroはMITライセンスのオープンソースとして維持される方針が明言されており、Nuxtの作者であるSébastien Chopin氏やコアチームメンバーがVercelに参画しています。
Vercelは自社フレームワークのNext.jsでOpenTelemetryのネイティブサポートを既に実装済みです。NuxtLabsの買収により、同様のOTelサポートがNuxtにも追加される可能性がありますが、2026年2月時点で具体的なロードマップは公表されていません(参考: nuxt/nuxt Discussion #32559)。
現状の回避策
公式サポートがない現在、Nuxtアプリにトレーシングを追加するには以下のいずれかの方法を取ります。
- コミュニティ製Nuxtモジュールを使用する(推奨)
- Node.jsの
--require/--importフラグで計装ファイルを外部から注入する - Nuxtプラグインとして手動でOTel SDKを初期化する
Nuxt向けOpenTelemetryモジュール比較
コミュニティから3つの主要モジュールが公開されています。
| 項目 | @scayle/nuxt-opentelemetry | @hannoeru/nuxt-otel | nitro-opentelemetry |
|---|---|---|---|
| 最新バージョン | 0.17.3 | 0.0.8 | 0.10.2 |
| 対象レイヤー | Nitro(サーバー) | Nuxt全体(Node.js + Vercel) | Nitro(サーバー) |
| 自動計装 | HTTP Span Semantic Conventions v1.26.0準拠 | ゼロコンフィグで自動計装 | リクエスト・エラーの自動Span生成 |
| カスタム計装 | Nitroプラグインで拡張可能 | 関数トレーシング対応 | defineTracedEventHandlerユーティリティ |
| 設定方式 | ビルド時 + ランタイム両対応 | ゼロコンフィグ | ランタイムフック(otel:span:name等) |
| メンテナンス | 活発(2026年2月更新) | 初期段階(v0.0.x) | 活発(2026年1月更新) |
| npm | @scayle/nuxt-opentelemetry | @hannoeru/nuxt-otel | nitro-opentelemetry |
選定の目安:
- サーバーサイドのHTTPトレーシングを標準仕様に沿って実装したい → @scayle/nuxt-opentelemetry
- 最小限の設定でトレーシングを開始したい → @hannoeru/nuxt-otel
- Nitroサーバーに特化した柔軟なフック機構が必要 → nitro-opentelemetry
@scayle/nuxt-opentelemetryによる導入手順
最もアクティブにメンテナンスされている @scayle/nuxt-opentelemetry を使った導入手順を示します。
パッケージのインストール
npm install @scayle/nuxt-opentelemetry
# OTel SDKとOTLPエクスポーターも必要
npm install @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http
nuxt.config.tsへの設定追加
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@scayle/nuxt-opentelemetry'],
opentelemetry: {
serviceName: 'my-nuxt-app',
},
})
環境変数の設定
# .env
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_SERVICE_NAME=my-nuxt-app
この設定だけで、Nitroサーバーへのリクエストが自動的にトレースされ、指定したOTLPエンドポイントにスパンデータが送信されます。
手動セットアップ: プラグインでOTelを組み込む方法
モジュールを使わずに直接OTel SDKを統合したい場合の手順です。サーバーサイドとクライアントサイドで別々のアプローチが必要になります。
サーバーサイド計装(Nitro)
Node.jsの --import フラグを使って、アプリケーション起動前にOTel SDKを初期化します。
まず、計装ファイルを作成します。
// server/instrumentation.ts
import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'
const sdk = new NodeSDK({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'nuxt-server',
}),
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318/v1/traces',
}),
instrumentations: [getNodeAutoInstrumentations()],
})
sdk.start()
process.on('SIGTERM', () => {
sdk.shutdown().then(() => process.exit(0))
})
次に、package.json のスクリプトを修正します。
{
"scripts": {
"dev": "node --import ./server/instrumentation.ts node_modules/nuxi/bin/nuxi.mjs dev",
"start": "node --import ./server/instrumentation.ts .output/server/index.mjs"
}
}
必要なパッケージをインストールします。
npm install @opentelemetry/sdk-node \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/resources \
@opentelemetry/semantic-conventions
クライアントサイド計装(ブラウザ)
ブラウザ側のトレーシングにはNuxtプラグインを使います。
// plugins/otel.client.ts
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { ZoneContextManager } from '@opentelemetry/context-zone'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig()
const resource = resourceFromAttributes({
[ATTR_SERVICE_NAME]: config.public.otelServiceName || 'nuxt-client',
})
const exporter = new OTLPTraceExporter({
url: config.public.otelExporterEndpoint || 'http://localhost:4318/v1/traces',
})
const provider = new WebTracerProvider({
resource,
spanProcessors: [new BatchSpanProcessor(exporter)],
})
provider.register({
contextManager: new ZoneContextManager(),
})
})
nuxt.config.ts にランタイム設定を追加します。
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
public: {
otelServiceName: 'nuxt-client',
otelExporterEndpoint: 'http://localhost:4318/v1/traces',
},
},
})
ブラウザ側の計装に必要なパッケージです。
npm install @opentelemetry/sdk-trace-web \
@opentelemetry/sdk-trace-base \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/context-zone \
@opentelemetry/resources \
@opentelemetry/semantic-conventions
サーバーとクライアントのスパンを紐付ける
SSRアプリでは、サーバーで生成されたトレースIDをクライアント側に伝搬することで、1つのリクエストをサーバーからブラウザまで一貫して追跡できます。
Nitroのレスポンスヘッダーに traceparent を含め、クライアントプラグインでそのヘッダーを読み取って親スパンとして設定するのが一般的なパターンです。W3C Trace Context仕様に準拠した traceparent ヘッダーの形式は以下のとおりです。
traceparent: 00-{trace-id}-{parent-id}-{trace-flags}
テレメトリ送信先の選び方
収集したトレースデータを可視化・分析するバックエンドの選択肢を整理します。いずれもOTLP(OpenTelemetry Protocol)に対応しているため、エクスポーター設定の変更だけで送信先を切り替えられます。
| バックエンド | ライセンス | ストレージ | 特徴 | 適したケース |
|---|---|---|---|---|
| Jaeger | OSS(Apache 2.0) | Elasticsearch, Cassandra, Kafka | v2.0でOTelをコアに採用。適応型サンプリング | 分散トレーシング専用で軽量に始めたい場合 |
| Grafana Tempo | OSS(AGPLv3) | S3, GCS, Azure Blob | Grafanaエコシステムとの統合。オブジェクトストレージでコスト効率が高い | 既にGrafana/Prometheusを使っている場合 |
| SigNoz | OSS(MIT + EE) | ClickHouse | トレース・メトリクス・ログを単一UIで提供するフルスタックAPM | OSSでオールインワンの可観測性が欲しい場合 |
| Datadog | 商用SaaS | マネージド | Agent 6.32.0以降でOTLP直接受信対応。GenAI Semantic Conventionsもサポート | エンタープライズ環境で統合的な監視基盤がある場合 |
| New Relic | 商用SaaS(無料枠あり) | マネージド | OTLPネイティブ対応。サービスマップ・エラーインボックスと自動統合 | 既存のNew Relic環境にOTelデータを統合する場合 |
Jaegerを使ったローカル開発環境の構築
開発時にトレースを確認するには、Jaegerが最も手軽です。Docker Composeで起動できます。
# docker-compose.yml
services:
jaeger:
image: jaegertracing/jaeger:2
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
environment:
COLLECTOR_OTLP_ENABLED: "true"
docker compose up -d
Jaeger UIは http://localhost:16686 でアクセスでき、サービス名でフィルタしてトレースを確認できます。
Next.jsとの可観測性サポートの違い
同じフルスタックフレームワークであるNext.jsとNuxtでは、OpenTelemetry対応の成熟度に差があります。
| 比較項目 | Next.js | Nuxt 3 |
|---|---|---|
| ビルトインOTelサポート | あり(フレームワークが自動計装) | なし(Issue #24002がオープン) |
| 公式パッケージ | @vercel/otel(ゼロコンフィグ) | 公式なし。コミュニティモジュールのみ |
| 計装エントリポイント | instrumentation.ts(register()をexport) | なし。Node.js --import フラグで外部注入 |
| 開発モード | 標準で動作 | 回避策が必要(nuxi経由でフラグ注入) |
| 自動生成されるスパン | HTTP method+route、render route、fetch、API route等10種以上 | フレームワークからの自動スパンなし |
| Edge Runtime対応 | @vercel/otel で対応 | 未対応 |
Next.jsでは instrumentation.ts ファイルをプロジェクトルートに配置するだけでOTel SDKが初期化されます。一方、Nuxtでは同等のエントリポイントが存在しないため、起動スクリプトの修正やモジュールの導入が必要です。
この差は、NuxtLabsのVercel買収により将来的に縮まる可能性があります。ただし、現時点でNext.jsからNuxtへの移行を検討している場合は、可観測性の成熟度の違いを考慮に入れるべきです。
本番環境のベストプラクティス
サンプリング戦略の設定
すべてのリクエストをトレースすると、パフォーマンスへの影響とストレージコストが増大します。本番環境では適切なサンプリングレートを設定するのが重要です。
import { TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base'
const sdk = new NodeSDK({
sampler: new TraceIdRatioBasedSampler(0.1), // 10%のリクエストをトレース
// ... 他の設定
})
環境変数でも指定できます。
OTEL_TRACES_SAMPLER=traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1
BatchSpanProcessorの活用
ブラウザ側・サーバー側ともに、SimpleSpanProcessor ではなく BatchSpanProcessor を使用します。SimpleSpanProcessor はスパンごとに即座にエクスポートするため、高負荷時にパフォーマンスが劣化します。BatchSpanProcessor はスパンをバッファに蓄積し、一定量または一定時間ごとにまとめて送信します。
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
// バッチ設定のカスタマイズ
const processor = new BatchSpanProcessor(exporter, {
maxQueueSize: 2048, // キューの最大サイズ
maxExportBatchSize: 512, // 1回のエクスポートで送信する最大スパン数
scheduledDelayMillis: 5000, // エクスポート間隔(ミリ秒)
})
環境ごとの設定切り替え
Nuxtの runtimeConfig と環境変数を組み合わせて、開発・ステージング・本番で設定を切り替えます。
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
otelEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318',
otelServiceName: process.env.OTEL_SERVICE_NAME || 'nuxt-app',
public: {
otelEnabled: process.env.OTEL_ENABLED !== 'false',
},
},
})
機密情報の除外
トレースデータにユーザーの個人情報や認証トークンが含まれないよう、スパン属性のフィルタリングを行います。
import { SpanProcessor, ReadableSpan } from '@opentelemetry/sdk-trace-base'
class SanitizingSpanProcessor implements SpanProcessor {
private inner: SpanProcessor
constructor(inner: SpanProcessor) {
this.inner = inner
}
onEnd(span: ReadableSpan): void {
// Authorizationヘッダーやcookie情報を除去
const sanitized = { ...span }
this.inner.onEnd(sanitized)
}
// forceFlush, shutdown, onStart は inner に委譲
onStart(span: any, context: any) { this.inner.onStart(span, context) }
forceFlush() { return this.inner.forceFlush() }
shutdown() { return this.inner.shutdown() }
}
よくあるトラブルと対処法
トレースがバックエンドに表示されない
確認ポイント:
- OTLPエンドポイントのURLが正しいか(
http://localhost:4318はHTTP、http://localhost:4317はgRPC) - バックエンドがOTLPレシーバーを有効にしているか
- ファイアウォールやネットワークポリシーがポートをブロックしていないか
デバッグログを有効にして、スパンが生成されているか確認します。
OTEL_LOG_LEVEL=debug node --import ./server/instrumentation.ts .output/server/index.mjs
開発モード(npm run dev)でトレースが取れない
Nuxtの開発サーバーは nuxi 経由で起動されるため、--import フラグを直接渡す必要があります。
{
"scripts": {
"dev": "node --import ./server/instrumentation.ts node_modules/nuxi/bin/nuxi.mjs dev"
}
}
tsx や ts-node を使っている場合は、計装ファイルのトランスパイルも必要です。
クライアントサイドのスパンが送信されない
ブラウザからOTLPエンドポイントへの送信はCORS制約を受けます。バックエンド側でCORSヘッダーを適切に設定するか、OpenTelemetry Collectorをプロキシとして配置します。
# otel-collector-config.yaml
receivers:
otlp:
protocols:
http:
cors:
allowed_origins:
- "http://localhost:3000"
- "https://your-domain.com"
Node.jsのESMとCJSの互換性問題
Nuxt 3はESMベースですが、一部のOpenTelemetryパッケージがCJS形式で配布されています。--import フラグを使う場合、計装ファイルは .mjs 拡張子にするか、package.json に "type": "module" を指定します。
まとめ
Nuxt 3はフレームワーク本体にOpenTelemetryの統合機能を持ちませんが、コミュニティモジュールと手動セットアップにより、サーバーとクライアント両面の可観測性を確保できます。
導入の進め方としては、まず @scayle/nuxt-opentelemetry(v0.17.3)や nitro-opentelemetry(v0.10.2)などのモジュールでサーバーサイドのトレーシングを開始し、必要に応じてクライアントサイドのプラグインを追加するのが実用的です。
バックエンドは、ローカル開発ではJaeger、本番環境ではGrafana TempoやSigNozなどのOSSツール、またはDatadog・New Relicなどの商用SaaSから要件に合わせて選択できます。OTLPプロトコルに統一されているため、送信先の変更は環境変数の差し替えだけで完了します。
2025年7月のVercelによるNuxtLabs買収(出典: Vercel Blog)を受け、NuxtへのOTelネイティブサポート追加が期待されますが、公式な開発計画はまだ示されていません。現時点では本記事で紹介したコミュニティ製モジュールを活用し、将来の公式サポートへスムーズに移行できる構成を維持することが望ましいです。
関連リソース:
