Vue.js 可観測性:Sentry/OpenTelemetry/Web Vitals で UX を数値化
現代の Web アプリケーション開発において、ユーザー体験の質を把握することは非常に重要です。Vue.js で構築したアプリケーションがどれほど快適に動作しているのか、どこでエラーが発生しているのか、パフォーマンスのボトルネックはどこにあるのか。これらを「見える化」することで、ユーザーに最高の体験を提供できるようになります。
本記事では、Sentry、OpenTelemetry、Web Vitals という 3 つの強力なツールを組み合わせて、Vue.js アプリケーションの可観測性を高め、UX(ユーザーエクスペリエンス)を数値化する方法をご紹介しますね。これらのツールを導入すれば、開発チームは問題を素早く発見し、データドリブンな改善を実現できるでしょう。
背景
アプリケーション監視の重要性
Vue.js でフロントエンドアプリケーションを開発する際、開発環境では問題なく動作していても、本番環境では予期しないエラーが発生することがあります。ユーザーの利用環境は多様で、ブラウザの種類、ネットワーク速度、デバイスのスペックなど、さまざまな条件下で動作することになりますね。
このような複雑な環境下で、アプリケーションが正常に動作し続けているかを確認するには、可観測性(Observability) が欠かせません。
可観測性の 3 本柱
可観測性は、以下の 3 つの要素で構成されています。
| # | 要素 | 説明 |
|---|---|---|
| 1 | メトリクス | CPU やメモリ使用率、リクエスト数などの数値データ |
| 2 | ログ | アプリケーションが出力するイベントの記録 |
| 3 | トレース | リクエストがシステムを通過する際の経路と処理時間 |
これら 3 つを組み合わせることで、システムの内部状態を外部から理解できるようになります。
以下の図は、可観測性の 3 本柱がどのように連携してシステムの健全性を監視するかを示しています。
mermaidflowchart TB
app["Vue.js<br/>アプリケーション"]
metrics["メトリクス<br/>(数値データ)"]
logs["ログ<br/>(イベント記録)"]
traces["トレース<br/>(処理経路)"]
platform["監視プラットフォーム"]
app --> metrics
app --> logs
app --> traces
metrics --> platform
logs --> platform
traces --> platform
platform --> insight["インサイト<br/>(問題発見と改善)"]
この図からわかるように、Vue.js アプリケーションから収集した 3 種類のデータを統合的に分析することで、より深いインサイトが得られるのです。
UX 数値化の必要性
ユーザー体験は主観的な概念ですが、これを客観的に評価するには数値化が必要です。「なんとなく遅い」「たまにエラーが出る」といった曖昧な表現ではなく、「ページ読み込みに 3.5 秒かかっている」「エラー発生率が 0.8%」といった具体的な数値で把握できれば、改善の優先順位を決めやすくなりますね。
課題
Vue.js アプリケーションにおける監視の難しさ
Vue.js はクライアントサイドで動作するため、従来のサーバーサイドアプリケーションとは異なる課題があります。
| # | 課題 | 詳細 |
|---|---|---|
| 1 | エラーの把握困難 | ユーザーのブラウザで発生したエラーは開発者に届きにくい |
| 2 | パフォーマンス測定 | デバイスやネットワーク環境によって性能が大きく変動する |
| 3 | ユーザー行動の追跡 | どの機能がどの程度使われているか把握しづらい |
| 4 | 非同期処理の複雑さ | API 呼び出しやコンポーネントライフサイクルの追跡が複雑 |
既存のツールだけでは不十分
ブラウザの開発者ツールや Vue Devtools は開発時には便利ですが、本番環境での監視には使えません。また、単一のツールだけでは可観測性の全体をカバーできないケースが多いのです。
以下の図は、Vue.js アプリケーションで発生する典型的な問題フローを示しています。
mermaidsequenceDiagram
participant user as ユーザー
participant vue as Vue.js App
participant api as Backend API
participant db as データベース
user->>vue: ページアクセス
Note over vue: コンポーネント<br/>マウント処理
vue->>api: データ取得リクエスト
Note over api: 処理遅延発生
api->>db: クエリ実行
db-->>api: データ返却
Note over api: タイムアウト発生
api-->>vue: エラーレスポンス
Note over vue: エラー表示失敗
vue-->>user: 白い画面
Note over user: 何が起きたか<br/>わからない
この図が示すように、エラーがどこで発生したのか、何が原因なのかを特定するのは容易ではありません。ユーザーは問題を報告してくれないことも多く、開発者は問題の存在すら気づけないこともあります。
データの統合と分析の困難さ
パフォーマンスデータ、エラーログ、ユーザー行動データがそれぞれ別のツールに散在していると、全体像を把握するのが難しくなります。データを統合し、相関関係を見つけることが課題となるでしょう。
解決策
Sentry、OpenTelemetry、Web Vitals の組み合わせ
これらの課題を解決するため、3 つのツールを組み合わせたアプローチをご紹介します。
| # | ツール | 主な役割 | 収集データ |
|---|---|---|---|
| 1 | Sentry | エラー監視・トラッキング | エラースタック、ユーザーコンテキスト |
| 2 | OpenTelemetry | 分散トレーシング・メトリクス収集 | トレース、スパン、カスタムメトリクス |
| 3 | Web Vitals | UX パフォーマンス測定 | LCP、FID、CLS などの Core Web Vitals |
各ツールの役割と特徴
Sentryは、JavaScript エラーを自動的にキャプチャし、スタックトレース、ブラウザ情報、ユーザー操作履歴などの詳細な情報を提供します。エラーが発生した際の状況を再現しやすくなるため、バグ修正が格段に早くなりますね。
OpenTelemetryは、ベンダーニュートラルな計測フレームワークで、トレースやメトリクスを標準化された方法で収集できます。Vue.js アプリケーションから API サーバー、データベースまで、リクエストの全経路を追跡できるのが強みでしょう。
Web Vitalsは、Google が定義したユーザー体験の品質指標です。ページの読み込み速度、インタラクティブ性、視覚的安定性を数値化し、SEO にも影響を与えます。
以下の図は、これら 3 つのツールがどのように連携して Vue.js アプリケーションの可観測性を実現するかを示しています。
mermaidflowchart LR
vue["Vue.js App"]
subgraph monitoring["監視システム"]
sentry["Sentry<br/>(エラー監視)"]
otel["OpenTelemetry<br/>(トレーシング)"]
vitals["Web Vitals<br/>(UX指標)"]
end
subgraph data["収集データ"]
errors["エラー情報"]
traces["トレースデータ"]
metrics["パフォーマンス<br/>メトリクス"]
end
vue --> sentry
vue --> otel
vue --> vitals
sentry --> errors
otel --> traces
vitals --> metrics
errors --> dashboard["統合<br/>ダッシュボード"]
traces --> dashboard
metrics --> dashboard
dashboard --> action["改善<br/>アクション"]
図で理解できる要点:
- 各ツールが異なる側面から Vue.js アプリを監視
- 収集したデータを統合ダッシュボードで一元管理
- データに基づいた改善アクションを実行可能
統合による相乗効果
これら 3 つのツールを組み合わせることで、以下のような相乗効果が得られます。
エラーが発生した際、Sentry でエラー内容を確認しつつ、OpenTelemetry のトレースでどの API 呼び出しが失敗したかを特定し、Web Vitals でそのエラーが UX にどの程度影響を与えたかを数値で把握できるのです。
具体例
環境構築とパッケージインストール
まず、必要なパッケージをインストールしましょう。Yarn を使って以下のコマンドを実行します。
bash# Sentryの公式Vue.jsプラグイン
yarn add @sentry/vue @sentry/tracing
# OpenTelemetryのコアパッケージ
yarn add @opentelemetry/api @opentelemetry/sdk-trace-web
# Web Vitals測定ライブラリ
yarn add web-vitals
このコマンドで、3 つの監視ツールの基礎となるパッケージがインストールされます。
Sentry の初期設定
Vue.js アプリケーションに Sentry を統合する際は、アプリケーションの初期化時に設定を行います。以下はmain.tsでの設定例です。
typescript// main.ts
import { createApp } from 'vue';
import * as Sentry from '@sentry/vue';
import App from './App.vue';
const app = createApp(App);
次に、Sentry の初期化設定を行います。DSN(Data Source Name)は Sentry プロジェクトで取得したものを使用してください。
typescript// Sentry初期化設定
Sentry.init({
app,
dsn: 'YOUR_SENTRY_DSN', // Sentryダッシュボードから取得
environment: process.env.NODE_ENV, // 開発・本番環境の識別
integrations: [
// ブラウザトレーシングを有効化
new Sentry.BrowserTracing({
tracePropagationTargets: [
'localhost',
/^https:\/\/api\.yourapp\.com/,
],
}),
],
});
トレースサンプリングレートとエラーのリプレイ機能を設定します。本番環境では全トラフィックを記録するとコストがかかるため、適切なサンプリングレートを設定しましょう。
typescript// サンプリングとリプレイの設定
Sentry.init({
// ... 前述の設定に追加
tracesSampleRate: 0.1, // 10%のトランザクションをトレース
replaysSessionSampleRate: 0.1, // 10%のセッションを記録
replaysOnErrorSampleRate: 1.0, // エラー発生時は100%記録
});
app.mount('#app');
この設定により、Vue.js アプリケーションで発生したエラーが自動的に Sentry に送信されるようになります。
カスタムエラーハンドリング
Vue.js コンポーネント内で意図的にエラー情報を送信したい場合は、以下のように実装します。
typescript// components/UserProfile.vue
import {
captureException,
captureMessage,
} from '@sentry/vue';
// 非同期処理のエラーハンドリング例
async function fetchUserData(userId: string) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return await response.json();
} catch (error) {
// エラーをSentryに送信
captureException(error);
throw error;
}
}
重要な処理の開始や完了をログとして記録する場合は、captureMessageを使用します。
typescript// 重要な処理のログ記録
function processPayment(amount: number) {
captureMessage(`決済処理開始: ${amount}円`, 'info');
// 決済処理の実装
// ...
captureMessage(`決済処理完了: ${amount}円`, 'info');
}
OpenTelemetry の設定
OpenTelemetry を使用して、Vue.js アプリケーションのトレーシングを実装します。まず、必要なモジュールをインポートします。
typescript// telemetry.ts
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
次に、トレーサープロバイダーを設定し、エクスポーターを構成します。OTLP エクスポーターは、収集したトレースデータをバックエンドに送信する役割を担います。
typescript// トレーサープロバイダーの初期化
const provider = new WebTracerProvider({
resource: {
attributes: {
'service.name': 'vue-app', // サービス名
'service.version': '1.0.0', // バージョン
},
},
});
エクスポーターとスパンプロセッサーを設定します。バッチ処理により、効率的にデータを送信できますね。
typescript// OTLPエクスポーターの設定
const exporter = new OTLPTraceExporter({
url: 'https://your-collector-endpoint/v1/traces',
headers: {
Authorization: 'Bearer YOUR_API_TOKEN',
},
});
// バッチスパンプロセッサーの追加
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
provider.register();
自動計測機能を有効化することで、XMLHttpRequest や Fetch API の呼び出しを自動的にトレースできます。
typescript// 自動計測の登録
registerInstrumentations({
instrumentations: [
new XMLHttpRequestInstrumentation(),
new FetchInstrumentation(),
],
});
カスタムスパンの作成
Vue.js コンポーネント内で特定の処理をトレースしたい場合は、カスタムスパンを作成します。
typescript// composables/useTracing.ts
import { trace } from '@opentelemetry/api';
export function useTracing() {
const tracer = trace.getTracer('vue-app');
// カスタムスパンを作成する関数
function traceOperation<T>(
name: string,
operation: () => Promise<T>
): Promise<T> {
return tracer.startActiveSpan(name, async (span) => {
try {
const result = await operation();
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (error) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
});
throw error;
} finally {
span.end();
}
});
}
return { traceOperation };
}
この Composable を使用することで、任意の非同期処理をトレースできるようになります。
typescript// コンポーネントでの使用例
import { useTracing } from '@/composables/useTracing';
const { traceOperation } = useTracing();
async function loadUserData() {
await traceOperation('load-user-data', async () => {
const response = await fetch('/api/user');
return await response.json();
});
}
Web Vitals の測定と送信
Web Vitals ライブラリを使用して、Core Web Vitals を測定し、結果を監視システムに送信します。
typescript// vitals.ts
import {
onCLS,
onFID,
onLCP,
onFCP,
onTTFB,
} from 'web-vitals';
import { captureMessage } from '@sentry/vue';
各指標の測定結果を受け取り、Sentry に送信する関数を定義します。メトリクス名、値、評価(good/needs-improvement/poor)を含めましょう。
typescript// Web Vitalsメトリクスの送信
function sendToAnalytics(metric: Metric) {
// Sentryにカスタムメトリクスとして送信
captureMessage(`Web Vitals: ${metric.name}`, {
level: 'info',
tags: {
metric_name: metric.name,
metric_value: metric.value.toString(),
metric_rating: metric.rating, // good/needs-improvement/poor
},
});
}
各 Core Web Vitals 指標の測定を開始します。これらはユーザーのブラウザで自動的に計測されます。
typescript// Core Web Vitalsの測定開始
export function initWebVitals() {
// Largest Contentful Paint(最大コンテンツの描画時間)
onLCP(sendToAnalytics);
// First Input Delay(初回入力遅延)
onFID(sendToAnalytics);
// Cumulative Layout Shift(累積レイアウトシフト)
onCLS(sendToAnalytics);
// First Contentful Paint(初回コンテンツの描画時間)
onFCP(sendToAnalytics);
// Time to First Byte(初回バイト受信時間)
onTTFB(sendToAnalytics);
}
アプリケーションの初期化時にこの関数を呼び出すことで、自動的に Web Vitals の測定が開始されます。
typescript// main.ts で呼び出し
import { initWebVitals } from './vitals';
// アプリケーションマウント後に測定開始
app.mount('#app');
initWebVitals();
OpenTelemetry と Web Vitals の統合
Web Vitals のデータを OpenTelemetry のメトリクスとしても送信することで、より包括的な分析が可能になります。
typescript// vitals-otel.ts
import { metrics } from '@opentelemetry/api';
import { onLCP, onFID, onCLS } from 'web-vitals';
const meter = metrics.getMeter('web-vitals');
各指標用のヒストグラムメトリクスを作成します。ヒストグラムは値の分布を記録するのに適しています。
typescript// メトリクスの作成
const lcpHistogram = meter.createHistogram(
'web.vitals.lcp',
{
description: 'Largest Contentful Paint',
unit: 'ms',
}
);
const fidHistogram = meter.createHistogram(
'web.vitals.fid',
{
description: 'First Input Delay',
unit: 'ms',
}
);
const clsHistogram = meter.createHistogram(
'web.vitals.cls',
{
description: 'Cumulative Layout Shift',
unit: 'score',
}
);
測定結果を OpenTelemetry のメトリクスとして記録します。
typescript// Web VitalsをOpenTelemetryメトリクスとして記録
export function initWebVitalsWithOTel() {
onLCP((metric) => {
lcpHistogram.record(metric.value, {
rating: metric.rating,
navigation_type: metric.navigationType,
});
});
onFID((metric) => {
fidHistogram.record(metric.value, {
rating: metric.rating,
});
});
onCLS((metric) => {
clsHistogram.record(metric.value, {
rating: metric.rating,
});
});
}
エラーとパフォーマンスの相関分析
Sentry のコンテキスト機能を使用して、エラー発生時のパフォーマンス情報を付加できます。
typescript// error-context.ts
import { setContext, captureException } from '@sentry/vue';
import { onLCP, onFID, onCLS } from 'web-vitals';
// パフォーマンスメトリクスを保持
let performanceMetrics = {
lcp: 0,
fid: 0,
cls: 0,
};
// メトリクスの更新
onLCP((metric) => {
performanceMetrics.lcp = metric.value;
});
onFID((metric) => {
performanceMetrics.fid = metric.value;
});
onCLS((metric) => {
performanceMetrics.cls = metric.value;
});
エラー発生時に、その時点でのパフォーマンスメトリクスをコンテキストとして添付します。これにより、パフォーマンスの悪化とエラーの関連性を分析できますね。
typescript// エラーキャプチャ時にパフォーマンス情報を添付
export function captureErrorWithMetrics(error: Error) {
setContext('performance', {
lcp: performanceMetrics.lcp,
fid: performanceMetrics.fid,
cls: performanceMetrics.cls,
});
captureException(error);
}
ルーターナビゲーションのトレーシング
Vue Router のナビゲーションをトレースすることで、ページ遷移のパフォーマンスを測定できます。
typescript// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import { trace } from '@opentelemetry/api';
const router = createRouter({
history: createWebHistory(),
routes: [
/* ルート定義 */
],
});
const tracer = trace.getTracer('vue-router');
ナビゲーションガードを使用して、ルート遷移のトレーシングを実装します。
typescript// ルート遷移のトレーシング
router.beforeEach((to, from, next) => {
const span = tracer.startSpan('route-navigation', {
attributes: {
'route.from': from.path,
'route.to': to.path,
'route.name': to.name?.toString() || 'unknown',
},
});
// スパンをルートメタに保存
to.meta.span = span;
next();
});
ナビゲーション完了時にスパンを終了します。これにより、ページ遷移にかかった時間を正確に測定できるでしょう。
typescriptrouter.afterEach((to) => {
const span = to.meta.span;
if (span) {
span.end();
}
});
export default router;
カスタムダッシュボードの構築
収集したデータを可視化するため、Vue.js コンポーネントでリアルタイムダッシュボードを構築することもできます。
typescript// components/MetricsDashboard.vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
interface Metrics {
errorRate: number
avgResponseTime: number
lcp: number
fid: number
cls: number
}
const metrics = ref<Metrics>({
errorRate: 0,
avgResponseTime: 0,
lcp: 0,
fid: 0,
cls: 0,
})
定期的にメトリクスを取得し、ダッシュボードに表示します。
typescript// メトリクスの取得
async function fetchMetrics() {
try {
const response = await fetch('/api/metrics')
metrics.value = await response.json()
} catch (error) {
console.error('メトリクス取得失敗', error)
}
}
// 30秒ごとに更新
onMounted(() => {
fetchMetrics()
setInterval(fetchMetrics, 30000)
})
</script>
パフォーマンス最適化のワークフロー
以下の図は、可観測性データを活用したパフォーマンス改善のワークフローを示しています。
mermaidflowchart TD
start["監視開始"] --> collect["データ収集<br/>(Sentry/OTel/Vitals)"]
collect --> analyze["データ分析"]
analyze --> check{"パフォーマンス<br/>基準を満たす?"}
check -->|Yes| monitor["継続監視"]
check -->|No| identify["ボトルネック特定"]
identify --> prioritize["優先度付け<br/>(UX影響度順)"]
prioritize --> implement["改善実装"]
implement --> test["効果測定"]
test --> collect
monitor --> alert{"アラート<br/>発生?"}
alert -->|Yes| identify
alert -->|No| monitor
図で理解できる要点:
- データ収集から改善実装までの継続的なサイクル
- パフォーマンス基準に基づいた自動判定
- アラート機能による問題の早期発見
エラー率とパフォーマンスの関連性
実際の運用では、エラー率とパフォーマンス指標の相関関係を分析することが重要です。
| # | パフォーマンス状態 | エラー率 | LCP 平均 | 改善施策 |
|---|---|---|---|---|
| 1 | 良好 | 0.1%以下 | 2.5s 以下 | 現状維持 |
| 2 | 要注意 | 0.1-0.5% | 2.5-4.0s | 部分最適化 |
| 3 | 問題あり | 0.5-1.0% | 4.0-5.0s | 優先的改善 |
| 4 | 深刻 | 1.0%以上 | 5.0s 以上 | 緊急対応必要 |
このような基準を設けることで、チーム全体で改善の優先度を共有できますね。
まとめ
Vue.js アプリケーションの可観測性を高めることは、ユーザーに最高の体験を提供するための必須条件です。Sentry、OpenTelemetry、Web Vitals という 3 つのツールを組み合わせることで、エラー監視、分散トレーシング、UX パフォーマンス測定を統合的に実現できます。
本記事でご紹介した実装方法を活用すれば、以下のような効果が期待できるでしょう。
まず、エラーが発生した際に即座に通知を受け取り、詳細なコンテキスト情報から原因を特定できるようになります。次に、API レスポンス時間やページ読み込み速度などのパフォーマンスメトリクスを継続的に監視し、劣化を早期に発見できますね。さらに、Core Web Vitals を数値化することで、SEO 対策と UX 改善を同時に進められます。
可観測性は一度導入して終わりではなく、継続的にデータを収集・分析し、改善を重ねていくことが大切です。ユーザーの行動データとパフォーマンス指標を組み合わせることで、より深いインサイトが得られ、データドリブンな意思決定が可能になるのです。
これらのツールを活用して、ユーザーにとって快適で信頼性の高い Vue.js アプリケーションを構築していきましょう。
関連リンク
articleVue.js 可観測性:Sentry/OpenTelemetry/Web Vitals で UX を数値化
articleVue.js クリーンアーキテクチャ:Composable・サービス層・依存逆転の型
articleVue.js Router 速見表:ガード・遅延ロード・トランジションの定石
articleVue.js Monorepo 構築:pnpm/Turborepo でアプリとパッケージを一元管理
articleVue.js ルーター戦略比較:ネスト/動的セグメント/ガードの設計コスト
articleVue.js でメモリリーク?watch/effect/イベント登録の落とし穴と検知法
articleCline で何が自動化できる?設計・実装・テスト・運用のユースケース地図
articleWeb Components で作るアクセシブルなタブ UI:キーボード操作& ARIA 完備
articleVue.js 可観測性:Sentry/OpenTelemetry/Web Vitals で UX を数値化
articleClaude Code SRE 実務:レート制限・キューイング・指数バックオフの実装指針
articleAnsible 実行モデル解体新書:コントローラからターゲットまでの裏側
articleACF かブロックか:WordPress 入力 UI の設計判断と移行戦略
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来