Vite 本番の可観測性:ソースマップアップロードと Sentry 連携でエラーを特定

本番環境で発生したエラーを特定するのは、想像以上に困難です。Vite でビルドされたアプリケーションは、コードが圧縮・難読化されているため、エラースタックトレースを見ても元のコードの位置がわかりません。そこで重要になるのが、ソースマップと Sentry を活用した可観測性の確保ですね。
本記事では、Vite で構築したアプリケーションにおいて、本番環境のエラーを効率的に特定するための Sentry 連携とソースマップアップロードの実装方法を解説します。実際に動作する設定例とともに、エラー監視の仕組みを体系的に理解できる内容となっています。
背景
本番環境でのエラー特定の難しさ
Vite は高速なビルドツールとして人気ですが、本番ビルド時にはコードを最適化・圧縮します。その結果、ブラウザで発生したエラーのスタックトレースは以下のような読めない形式になってしまいます。
typescriptError: Cannot read property 'name' of undefined
at r.createElement (main.a3f2b1c4.js:1:1234)
at t (main.a3f2b1c4.js:2:5678)
開発環境では明確だったエラー位置が、本番環境では main.a3f2b1c4.js:1:1234
のような情報しか得られません。これでは、どのコンポーネントのどの行でエラーが発生したのか判断できませんね。
ソースマップの役割
ソースマップは、圧縮されたコードと元のソースコードをマッピングするファイルです。.js.map
という拡張子を持ち、ビルドツールが自動生成します。
以下の図は、ソースマップを介してエラー位置を特定する流れを示しています。
mermaidflowchart LR
browser["ブラウザ"] -->|圧縮エラー| sentry["Sentry"]
sentry -->|ソースマップ参照| map["ソースマップ<br/>(*.js.map)"]
map -->|元の位置特定| source["元のソースコード"]
source -->|可読エラー| dev["開発者"]
ソースマップがあることで、UserProfile.tsx:42
のように、元のファイル名と行番号でエラーを表示できるようになります。エラーの原因特定が劇的に効率化されるでしょう。
Sentry の可観測性
Sentry は、エラー監視とパフォーマンストラッキングを提供する SaaS プラットフォームです。以下の機能により、本番環境の可観測性を向上させます。
# | 機能 | 説明 |
---|---|---|
1 | エラー収集 | ブラウザやサーバーで発生した全エラーを自動収集 |
2 | スタックトレース解析 | ソースマップを用いて元のコード位置を表示 |
3 | コンテキスト情報 | ユーザー情報、ブラウザ、OS などの環境情報を記録 |
4 | アラート通知 | 重大なエラー発生時に Slack や Email で通知 |
5 | パフォーマンス監視 | ページ読み込み時間やトランザクション追跡 |
このように、Sentry を導入することで、本番環境での問題を迅速に発見し、対処できる体制が整います。
課題
セキュリティリスクとソースマップの管理
ソースマップを本番環境に公開すると、以下のようなセキュリティリスクが発生します。
- ソースコードの露出: ソースマップには元のコードがほぼそのまま含まれており、ビジネスロジックや API キーが漏洩する可能性があります
- 攻撃対象の拡大: 攻撃者がコード構造を把握することで、脆弱性を発見しやすくなります
- 知的財産の保護: 独自のアルゴリズムや実装詳細が競合他社に知られるリスクがあります
一方、ソースマップがないと以下の問題が発生します。
# | 課題 | 影響 |
---|---|---|
1 | エラー位置不明 | デバッグに数時間〜数日かかる |
2 | 再現困難 | ユーザー環境でのみ発生するエラーを特定できない |
3 | パフォーマンス分析不可 | ボトルネックの特定が困難 |
このジレンマを解決するのが、Sentry へのプライベートなソースマップアップロードです。
以下の図は、課題の構造を示しています。
mermaidflowchart TB
prod["本番環境"] --> choice{"ソースマップを<br/>公開する?"}
choice -->|Yes| risk["セキュリティリスク<br/>・コード露出<br/>・攻撃対象拡大"]
choice -->|No| debug["デバッグ困難<br/>・エラー位置不明<br/>・再現不可"]
risk --> solution["解決策"]
debug --> solution
solution --> private["プライベートアップロード<br/>Sentry へのみ送信"]
公開するとリスク、公開しないとデバッグ困難という二律背反の状況を、Sentry へのプライベートアップロードで解消します。
ビルドプロセスへの統合
ソースマップアップロードを手動で行うのは、以下の理由から現実的ではありません。
- ビルドごとに変化: ハッシュ付きファイル名(
main.a3f2b1c4.js
)がビルドごとに変わるため、手動では追跡できません - CI/CD との整合: 自動デプロイメントパイプラインに組み込まないと、アップロード漏れが発生します
- 複数環境: ステージング、本番など複数環境で異なるビルドを管理する必要があります
そのため、Vite のビルドプロセスに統合された自動アップロードの仕組みが不可欠ですね。
解決策
Vite プラグインによる自動化
Sentry 公式が提供する @sentry/vite-plugin
を使用することで、ビルド時に自動的にソースマップをアップロードできます。このプラグインは以下の処理を自動で行います。
- ビルド後のソースマップファイルを検出
- Sentry API を使用してアップロード
- リリース情報(バージョン、コミットハッシュなど)を紐付け
- アップロード後、オプションでローカルのソースマップを削除
以下の図は、ビルドからアップロードまでの自動化フローを示しています。
mermaidflowchart LR
build["vite build"] --> plugin["@sentry/vite-plugin"]
plugin --> detect["ソースマップ検出"]
detect --> upload["Sentry へアップロード"]
upload --> associate["リリース情報紐付け"]
associate --> cleanup["ローカル削除<br/>(オプション)"]
cleanup --> dist["dist/ (*.js のみ)"]
このフローにより、開発者はビルドコマンドを実行するだけで、ソースマップ管理が完結します。手動作業が不要になり、ヒューマンエラーも防げますね。
環境変数による設定管理
本番ビルド時のみソースマップをアップロードするため、環境変数で制御します。以下の環境変数を .env.production
に設定しましょう。
bash# Sentry 設定
VITE_SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0
VITE_SENTRY_ORG=your-org-name
VITE_SENTRY_PROJECT=your-project-name
SENTRY_AUTH_TOKEN=your-auth-token
各環境変数の役割は以下の通りです。
# | 環境変数 | 説明 | 公開可否 |
---|---|---|---|
1 | VITE_SENTRY_DSN | Sentry プロジェクトの公開エンドポイント | ○ クライアント側で使用 |
2 | VITE_SENTRY_ORG | Sentry 組織名 | ○ アップロード用 |
3 | VITE_SENTRY_PROJECT | Sentry プロジェクト名 | ○ アップロード用 |
4 | SENTRY_AUTH_TOKEN | API アクセストークン | × 絶対に秘匿 |
SENTRY_AUTH_TOKEN
は Sentry の設定画面から生成できます。このトークンには project:releases
および org:read
のスコープが必要ですね。
プライベートアップロードの仕組み
Sentry へのソースマップアップロードは、以下の特徴により安全性が保たれます。
- API 認証: Auth Token による認証が必須で、権限のないユーザーはアクセスできません
- プライベート保存: アップロードされたソースマップは Sentry 内部にのみ保存され、公開 URL は生成されません
- アクセス制御: Sentry プロジェクトのメンバーのみがソースマップを参照可能です
- 本番コードとの分離: 本番環境にデプロイされる
dist/
フォルダには.js.map
ファイルを含めません
この仕組みにより、エンドユーザーはソースマップにアクセスできず、開発チームのみが詳細なエラー情報を取得できます。
具体例
プロジェクトのセットアップ
まず、必要なパッケージをインストールします。Sentry の SDK とビルドプラグインの両方が必要です。
bashyarn add @sentry/react
yarn add -D @sentry/vite-plugin
@sentry/react
はブラウザでのエラー捕捉を担当し、@sentry/vite-plugin
はビルド時のソースマップアップロードを担当します。
Vite 設定ファイルの実装
次に、vite.config.ts
でプラグインを設定しましょう。以下のコードは、環境変数を読み込み、条件付きでプラグインを有効化します。
typescriptimport { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';
import { sentryVitePlugin } from '@sentry/vite-plugin';
export default defineConfig(({ mode }) => {
// 環境変数の読み込み
const env = loadEnv(mode, process.cwd(), '');
return {
plugins: [
react(),
],
};
});
環境変数を loadEnv
で読み込むことで、.env.production
の値にアクセスできるようになります。次に、プラグインの条件付き追加を実装しましょう。
typescriptexport default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
// プラグインの配列
const plugins = [react()];
// 本番ビルド時のみ Sentry プラグインを追加
if (mode === 'production' && env.SENTRY_AUTH_TOKEN) {
plugins.push(
sentryVitePlugin({
org: env.VITE_SENTRY_ORG,
project: env.VITE_SENTRY_PROJECT,
authToken: env.SENTRY_AUTH_TOKEN,
})
);
}
return {
plugins,
};
});
この条件分岐により、開発環境ではプラグインが無効化され、ビルド時間が短縮されます。本番ビルド(mode === 'production'
)かつ認証トークンが設定されている場合のみ、ソースマップアップロードが実行されますね。
ソースマップ生成の設定
Vite にソースマップを生成させるため、build.sourcemap
オプションを設定します。本番環境でのみ hidden
モードを使用することで、セキュリティを保ちましょう。
typescriptexport default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
const plugins = [react()];
if (mode === 'production' && env.SENTRY_AUTH_TOKEN) {
plugins.push(
sentryVitePlugin({
org: env.VITE_SENTRY_ORG,
project: env.VITE_SENTRY_PROJECT,
authToken: env.SENTRY_AUTH_TOKEN,
})
);
}
return {
plugins,
build: {
// 本番では hidden、開発では true
sourcemap: mode === 'production' ? 'hidden' : true,
},
};
});
sourcemap: 'hidden'
の動作は以下の通りです。
# | モード | 生成ファイル | ソースマップ参照 | セキュリティ |
---|---|---|---|---|
1 | true | *.js , *.js.map | JS ファイルに //# sourceMappingURL=*.js.map 含む | ★ 低(公開される) |
2 | 'hidden' | *.js , *.js.map | JS ファイルに参照コメントなし | ★★★ 高(公開されない) |
3 | false | *.js のみ | 生成されない | ★★★★ 最高(エラー追跡不可) |
hidden
モードでは、.js.map
ファイルは生成されますが、ブラウザが読み込む JavaScript ファイルには参照が含まれません。そのため、ブラウザはソースマップを取得できず、Sentry のみが利用可能になります。
リリース情報の紐付け
ソースマップをリリースバージョンと紐付けることで、どのデプロイでエラーが発生したか特定できます。以下のコードで、Git のコミットハッシュをリリース名として使用しましょう。
typescriptimport { execSync } from 'child_process';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
const plugins = [react()];
if (mode === 'production' && env.SENTRY_AUTH_TOKEN) {
// Git コミットハッシュを取得
const release = execSync('git rev-parse HEAD').toString().trim();
plugins.push(
sentryVitePlugin({
org: env.VITE_SENTRY_ORG,
project: env.VITE_SENTRY_PROJECT,
authToken: env.SENTRY_AUTH_TOKEN,
release: {
name: release,
},
})
);
}
return {
plugins,
build: {
sourcemap: mode === 'production' ? 'hidden' : true,
},
};
});
release.name
にコミットハッシュを設定することで、Sentry 上でエラーと特定のコミットを関連付けられます。デプロイ履歴との照合が簡単になりますね。
アップロード後の自動削除
セキュリティをさらに強化するため、アップロード後にローカルのソースマップファイルを削除できます。filesToDeleteAfterUpload
オプションを使用しましょう。
typescriptif (mode === 'production' && env.SENTRY_AUTH_TOKEN) {
const release = execSync('git rev-parse HEAD').toString().trim();
plugins.push(
sentryVitePlugin({
org: env.VITE_SENTRY_ORG,
project: env.VITE_SENTRY_PROJECT,
authToken: env.SENTRY_AUTH_TOKEN,
release: {
name: release,
},
// アップロード後に削除するファイルパターン
filesToDeleteAfterUpload: ['dist/**/*.map'],
})
);
}
このオプションにより、dist/
フォルダ内の .map
ファイルがアップロード後に自動削除されます。デプロイパッケージにソースマップが含まれないため、誤って公開されるリスクがゼロになりますね。
ブラウザ側の Sentry 初期化
ビルド設定が完了したら、次はブラウザ側でエラーを捕捉する設定を行います。アプリケーションのエントリーポイント(通常は main.tsx
または index.tsx
)で Sentry を初期化しましょう。
typescriptimport React from 'react';
import ReactDOM from 'react-dom/client';
import * as Sentry from '@sentry/react';
import App from './App';
必要なモジュールをインポートします。@sentry/react
は React 専用の統合機能を提供し、コンポーネントエラーも自動的に捕捉してくれます。
typescript// Sentry 初期化(本番環境のみ)
if (import.meta.env.PROD) {
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.MODE,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
});
}
import.meta.env.PROD
で本番環境のみ初期化することで、開発環境でのノイズを防ぎます。browserTracingIntegration
はパフォーマンス監視を、replayIntegration
はエラー発生時のユーザー操作を記録します。
typescript// React アプリケーションのマウント
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
通常通り React アプリケーションをマウントします。Sentry の初期化は最初に実行されるため、アプリケーション全体のエラーを捕捉できますね。
リリースバージョンの同期
ビルド時に設定したリリースバージョンと、ブラウザ側の Sentry 初期化で使用するバージョンを一致させる必要があります。環境変数を使用して同期しましょう。
まず、vite.config.ts
でリリースバージョンを環境変数として定義します。
typescriptimport { execSync } from 'child_process';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
const plugins = [react()];
// リリースバージョンを取得
const release = mode === 'production'
? execSync('git rev-parse HEAD').toString().trim()
: 'dev';
if (mode === 'production' && env.SENTRY_AUTH_TOKEN) {
plugins.push(
sentryVitePlugin({
org: env.VITE_SENTRY_ORG,
project: env.VITE_SENTRY_PROJECT,
authToken: env.SENTRY_AUTH_TOKEN,
release: {
name: release,
},
filesToDeleteAfterUpload: ['dist/**/*.map'],
})
);
}
return {
plugins,
build: {
sourcemap: mode === 'production' ? 'hidden' : true,
},
// リリースバージョンを環境変数として注入
define: {
'import.meta.env.VITE_RELEASE': JSON.stringify(release),
},
};
});
define
オプションを使用することで、ビルド時に計算されたリリースバージョンをコード内で参照可能にします。
次に、ブラウザ側の初期化でこの値を使用します。
typescriptif (import.meta.env.PROD) {
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.MODE,
release: import.meta.env.VITE_RELEASE,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
});
}
これにより、Sentry がエラーを受信した際、正しいリリースのソースマップを使用してスタックトレースを復元できます。バージョンの不一致によるマッピングエラーが防げますね。
CI/CD パイプラインへの組み込み
GitHub Actions などの CI/CD 環境でビルドする場合、環境変数の設定が必要です。以下は GitHub Actions の設定例です。
yamlname: Build and Deploy
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0 # Git 履歴を取得(コミットハッシュ用)
fetch-depth: 0
により、完全な Git 履歴を取得します。これがないと git rev-parse HEAD
が正しく動作しません。
yaml - name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: yarn install --frozen-lockfile
依存関係をインストールします。--frozen-lockfile
により、yarn.lock
の整合性を保証できます。
yaml - name: Build application
env:
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
VITE_SENTRY_ORG: ${{ secrets.VITE_SENTRY_ORG }}
VITE_SENTRY_PROJECT: ${{ secrets.VITE_SENTRY_PROJECT }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: yarn build
GitHub Secrets に保存した環境変数を使用してビルドします。SENTRY_AUTH_TOKEN
は絶対に直接記述せず、Secrets 経由で渡しましょう。
yaml - name: Deploy to production
run: |
# デプロイコマンド(例: S3, Vercel, Netlify など)
yarn deploy
ビルド成果物をデプロイします。この時点で dist/
フォルダには .map
ファイルが含まれていないため、安全にデプロイできますね。
エラーの確認とデバッグ
デプロイ後、実際にエラーが発生したときの Sentry の動作を確認しましょう。以下のようなテストコードを本番環境で実行してみます(テスト後は削除してください)。
typescript// App.tsx
import { useEffect } from 'react';
import * as Sentry from '@sentry/react';
function App() {
useEffect(() => {
// テスト用エラー(本番確認後は削除)
const testError = () => {
throw new Error('Sentry テストエラー: ソースマップ確認用');
};
// 5秒後にエラーを発生させる
setTimeout(testError, 5000);
}, []);
return <div>アプリケーション</div>;
}
export default App;
このコードをデプロイして 5 秒待つと、Sentry ダッシュボードにエラーが表示されます。ソースマップが正しくアップロードされていれば、以下のような情報が確認できるでしょう。
# | 項目 | 正常時の表示 | 異常時の表示 |
---|---|---|---|
1 | ファイル名 | App.tsx | main.a3f2b1c4.js |
2 | 行番号 | 8 (実際の行) | 1 (圧縮後の行) |
3 | 関数名 | testError | r (圧縮後の名前) |
4 | コード表示 | 元のコードが表示される | 圧縮コードが表示される |
正常時には、元のソースコードが Sentry 上で確認でき、エラーの原因を即座に特定できます。
トラブルシューティング
ソースマップが正しく適用されない場合、以下の点を確認してください。
Error: Release not found
bashError: Release 'abc123...' not found in Sentry
このエラーは、ブラウザ側で指定したリリースバージョンとアップロードされたバージョンが一致しない場合に発生します。
解決方法:
vite.config.ts
のdefine
で定義したリリースバージョンを確認- Sentry ダッシュボードの「Releases」ページでアップロード済みバージョンを確認
git rev-parse HEAD
の実行結果が CI/CD 環境とローカルで一致するか確認
Error: Source map not found
bashError: Source map for 'main.a3f2b1c4.js' not found
ソースマップファイルがアップロードされていない、またはファイル名が一致していない場合に発生します。
解決方法:
build.sourcemap
が'hidden'
またはtrue
に設定されているか確認- ビルドログで
@sentry/vite-plugin
のアップロード成功メッセージを確認 SENTRY_AUTH_TOKEN
が正しく設定され、権限があるか確認
Error: SENTRY_AUTH_TOKEN is not set
bashError: SENTRY_AUTH_TOKEN environment variable is not set
認証トークンが設定されていない場合に発生します。
解決方法:
.env.production
ファイルにSENTRY_AUTH_TOKEN=...
が記載されているか確認- CI/CD 環境の Secrets 設定を確認
- トークンのスコープに
project:releases
とorg:read
が含まれているか確認
以下の図は、トラブルシューティングのフローを示しています。
mermaidflowchart TD
error["エラー発生"] --> check_release{"リリースバージョン<br/>一致?"}
check_release -->|No| fix_release["define 設定確認<br/>Git ハッシュ確認"]
check_release -->|Yes| check_map{"ソースマップ<br/>アップロード済み?"}
check_map -->|No| fix_upload["sourcemap 設定確認<br/>Auth Token 確認"]
check_map -->|Yes| check_token{"Auth Token<br/>有効?"}
check_token -->|No| fix_token["Token 再生成<br/>スコープ確認"]
check_token -->|Yes| success["正常動作"]
段階的に原因を切り分けることで、問題を効率的に解決できるでしょう。
まとめ
本記事では、Vite で構築したアプリケーションにおける本番環境の可観測性向上について、Sentry 連携とソースマップアップロードの実装方法を解説しました。
重要なポイントをまとめます。
- ソースマップの必要性: 本番環境の圧縮コードでは、エラー位置の特定が困難です。ソースマップがあることで、元のコードと紐付けてデバッグできます
- セキュリティとのバランス:
sourcemap: 'hidden'
により、ソースマップを生成しつつブラウザからはアクセス不可にできます。Sentry へのプライベートアップロードで、セキュリティとデバッグ効率を両立させましょう - 自動化の重要性:
@sentry/vite-plugin
を使用することで、ビルドプロセスに統合された自動アップロードが実現します。手動作業によるミスを防げますね - リリース管理: Git コミットハッシュをリリースバージョンとして使用することで、エラーと特定のデプロイを関連付けられます。デプロイ履歴との照合が容易になります
- CI/CD 統合: GitHub Actions などの CI/CD 環境で環境変数を適切に設定することで、自動ビルド・デプロイパイプラインに組み込めます
これらの仕組みを導入することで、本番環境で発生したエラーを迅速に特定し、ユーザー体験の向上とシステムの信頼性確保につながるでしょう。エラー監視は、サービスの品質を左右する重要な要素です。
関連リンク
- article
Vite 本番の可観測性:ソースマップアップロードと Sentry 連携でエラーを特定
- article
Micro Frontends 設計:`vite-plugin-federation` で分割可能な UI を構築
- article
【保存版】Vite 設定オプション早見表:`resolve` / `optimizeDeps` / `build` / `server`
- article
Vite で始める Preact:公式プラグイン設定と最短プロジェクト作成【完全手順】
- article
ブラウザランナー vs Node ランナー:Vitest 実行環境の技術比較と選定基準
- article
5 分で導入!Vite × Vitest 型付きユニットテスト環境の最短手順
- article
NestJS クリーンアーキテクチャ:UseCase/Domain/Adapter を疎結合に保つ設計術
- article
WebSocket プロトコル設計:バージョン交渉・機能フラグ・後方互換のパターン
- article
MySQL 読み書き分離設計:ProxySQL で一貫性とスループットを両立
- article
Motion(旧 Framer Motion)アニメオーケストレーション設計:timeline・遅延・相互依存の整理術
- article
WebRTC で遠隔支援:画面注釈・ポインタ共有・低遅延音声の実装事例
- article
JavaScript パフォーマンス最適化大全:レイアウトスラッシングを潰す実践テク
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来