T-CREATOR

Vue.js 本番運用チェックリスト:CSP/SRI/Cache-Control/エラーログの要点

Vue.js 本番運用チェックリスト:CSP/SRI/Cache-Control/エラーログの要点

Vue.js アプリケーションを本番環境へリリースする際、セキュリティとパフォーマンスの両面で考慮すべきポイントは多岐にわたります。本記事では、CSP(Content Security Policy)、SRI(Subresource Integrity)、Cache-Control、エラーログという 4 つの重要な要素に焦点を当て、実践的な設定方法と運用のコツをご紹介します。

これらの設定を適切に行うことで、XSS 攻撃のリスク軽減、改ざん検知、高速なページ表示、そして問題発生時の迅速な対応が可能になるでしょう。

背景

Vue.js アプリケーションの本番環境における課題

Vue.js で構築された SPA(Single Page Application)は、クライアントサイドで動的にコンテンツを生成するため、従来の静的サイトとは異なるセキュリティリスクを抱えています。

JavaScript の実行環境がブラウザ側にあることで、悪意のあるスクリプトの注入や外部リソースの改ざんといった脅威に晒される可能性があります。また、ビルド後の静的ファイルを効率的に配信し、ユーザー体験を向上させるためには、適切なキャッシュ戦略も欠かせません。

さらに本番環境では、予期しないエラーが発生した際に迅速に原因を特定し対処する仕組みが必要です。

以下の図は、Vue.js アプリケーションが本番環境で直面する主な課題領域を示しています。

mermaidflowchart TB
    subgraph security["セキュリティ領域"]
        xss["XSS攻撃リスク"]
        tamper["外部リソース<br/>改ざんリスク"]
    end

    subgraph performance["パフォーマンス領域"]
        cache["キャッシュ戦略<br/>の不備"]
        load["初回読み込み<br/>の遅延"]
    end

    subgraph monitoring["監視領域"]
        errorDetect["エラー検知<br/>の遅れ"]
        debug["デバッグ情報<br/>の不足"]
    end

    vueApp["Vue.js アプリ"] --> security
    vueApp --> performance
    vueApp --> monitoring

この図からわかるように、セキュリティ、パフォーマンス、監視の 3 つの観点でバランスよく対策を講じることが、安定した本番運用の鍵となります。

なぜ CSP・SRI・Cache-Control・エラーログが重要なのか

CSP はブラウザに対して「どこからのリソース読み込みを許可するか」を宣言することで、XSS 攻撃の影響を最小限に抑えます。SRI は外部 CDN から読み込むスクリプトやスタイルシートが改ざんされていないかをハッシュ値で検証する仕組みです。

Cache-Control は HTTP ヘッダーを通じてブラウザや CDN にキャッシュの振る舞いを指示し、無駄な通信を削減してページ表示を高速化します。エラーログは本番環境で発生した問題をリアルタイムに収集し、開発チームが素早く対応できるようにするための情報源となります。

これら 4 つの要素は、それぞれ異なる役割を担いながらも、総合的に Vue.js アプリケーションの品質と信頼性を高める土台となるのです。

課題

セキュリティ面での脆弱性

Vue.js アプリケーションでは、ユーザー入力を v-html ディレクティブで直接レンダリングしたり、外部 API から取得したデータを動的に表示したりする場面が多くあります。適切なサニタイズが行われていない場合、悪意のあるスクリプトが実行されてしまう XSS 攻撃のリスクが生じます。

また、CDN から Vue.js 本体や UI ライブラリを読み込む際、もしそのファイルが攻撃者によって改ざんされていたとしても、通常は検知する手段がありません。このような状況では、アプリケーション全体が危険に晒されることになります。

以下の図は、セキュリティ対策が不十分な場合に起こりうる攻撃フローを示しています。

mermaidsequenceDiagram
    participant attacker as 攻撃者
    participant cdn as CDN
    participant browser as ブラウザ
    participant vueApp as Vue.js アプリ

    attacker->>cdn: スクリプト改ざん
    browser->>cdn: vue.js リクエスト
    cdn->>browser: 改ざんされた<br/>vue.js 返却
    browser->>vueApp: 悪意のあるコード実行
    vueApp->>browser: ユーザー情報漏洩

この図が示すように、CDN のファイルが改ざんされると、ブラウザは疑うことなくそれを実行し、結果としてユーザー情報の漏洩やセッションハイジャックといった深刻な被害につながる可能性があります。

パフォーマンスとキャッシュ制御の問題

ビルド後の Vue.js アプリケーションは、通常 index.html、JavaScript バンドル、CSS ファイル、画像などの静的リソースで構成されます。これらのファイルに対して適切な Cache-Control ヘッダーが設定されていないと、毎回サーバーへリクエストが発生し、ページ表示が遅くなってしまいます。

逆に、キャッシュ期間を長く設定しすぎると、アプリケーションを更新してもユーザーのブラウザに古いファイルが残り続け、新機能が反映されないという問題が起こります。

特に index.html はエントリーポイントとなるファイルであり、常に最新版を取得する必要があるため、他のリソースとは異なるキャッシュ戦略が求められます。

エラー監視とデバッグの困難さ

開発環境では Vue Devtools やコンソールログで簡単にエラーを確認できますが、本番環境ではそうはいきません。ユーザーのブラウザで発生したエラーは、開発者の手元には届かないのです。

また、Vue.js のプロダクションビルドでは、コードが minify され変数名も難読化されるため、エラーメッセージだけでは原因の特定が難しくなります。エラーが発生したタイミング、ユーザーの操作内容、ブラウザ環境といった情報を体系的に収集する仕組みがないと、問題の再現も解決も困難になってしまいます。

解決策

CSP(Content Security Policy)の導入

CSP は HTTP レスポンスヘッダーまたは meta タグで設定することで、ブラウザに対してリソース読み込みのルールを伝えます。これにより、許可されていないソースからのスクリプト実行をブロックし、XSS 攻撃の被害を大幅に軽減できます。

CSP には複数のディレクティブがあり、スクリプト、スタイル、画像、フォントなど、リソースの種類ごとに読み込み元を指定できます。Vue.js アプリケーションでは、特に script-srcstyle-srcimg-srcconnect-src の設定が重要です。

以下の図は、CSP が設定されたブラウザにおけるリソース読み込みの判定フローを示しています。

mermaidflowchart TD
    request["リソース<br/>リクエスト発生"] --> checkCSP["CSP ポリシー<br/>確認"]
    checkCSP --> allowed{"許可された<br/>ソース?"}
    allowed -->|Yes| load["リソース読み込み"]
    allowed -->|No| block["読み込みブロック"]
    block --> report["CSP 違反<br/>レポート送信"]

このフローにより、想定外のソースからのリソースは自動的にブロックされ、違反内容をレポートとして収集することも可能です。

SRI(Subresource Integrity)の適用

SRI は <script> タグや <link> タグに integrity 属性を追加することで、リソースのハッシュ値を検証する仕組みです。ブラウザはリソースをダウンロード後、指定されたハッシュ値と一致するかをチェックし、改ざんが検知された場合は読み込みを拒否します。

特に CDN から Vue.js 本体や UI ライブラリを読み込む場合、SRI を設定することで万が一 CDN が侵害されても、改ざんされたファイルの実行を防げます。

SRI のハッシュアルゴリズムには sha256sha384sha512 があり、一般的には sha384 が推奨されています。

Cache-Control によるキャッシュ戦略

Cache-Control ヘッダーには max-ageno-cacheno-storeimmutable といったディレクティブがあり、これらを組み合わせることでリソースごとに最適なキャッシュ動作を定義できます。

Vue.js アプリケーションのキャッシュ戦略は、以下のように分類すると効果的です。

#ファイル種別キャッシュ戦略Cache-Control 設定例
1index.htmlキャッシュしないno-cache または max-age=0, must-revalidate
2JS/CSS(ハッシュ付き)長期キャッシュmax-age=31536000, immutable
3画像・フォント中期キャッシュmax-age=2592000
4API レスポンス短期または無効化max-age=60 または no-store

この表からわかるように、ファイルの更新頻度と重要度に応じてキャッシュ期間を調整することが、パフォーマンスと更新反映のバランスを取る鍵となります。

エラーログ収集の仕組み構築

本番環境でのエラー監視には、Vue.js のグローバルエラーハンドラーと外部エラートラッキングサービスを組み合わせる方法が効果的です。

Vue.js では app.config.errorHandler を設定することで、アプリケーション全体で発生したエラーをキャッチできます。さらに、ブラウザの window.onerrorwindow.addEventListener('unhandledrejection') を併用することで、Vue の管理外で発生したエラーも捕捉できます。

収集したエラー情報は、Sentry、LogRocket、Rollbar といったサービスに送信することで、エラーの発生頻度、影響を受けたユーザー数、スタックトレース、ユーザーの操作履歴などを一元管理できます。

以下の図は、エラーが発生してからログが収集されるまでのフローを示しています。

mermaidflowchart LR
    error["エラー発生"] --> handler["Vue エラー<br/>ハンドラー"]
    handler --> collect["エラー情報<br/>収集"]
    collect --> enrich["コンテキスト<br/>情報付加"]
    enrich --> send["外部サービス<br/>送信"]
    send --> dashboard["管理画面<br/>可視化"]

このフローにより、エラーが発生した瞬間から数秒以内に開発チームへ通知が届き、迅速な対応が可能になります。

具体例

CSP の実装例

CSP は Web サーバーの設定ファイルで HTTP ヘッダーとして追加するか、HTML の meta タグで定義します。ここでは両方の方法をご紹介します。

Nginx での CSP 設定

Nginx を使用している場合、設定ファイルに以下のように記述します。

nginx# Vue.js アプリケーション用の CSP 設定
add_header Content-Security-Policy "
  default-src 'self';
  script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self' data:;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
" always;

この設定では、基本的に自分のドメインからのリソースのみを許可し、特定の CDN や API サーバーへのアクセスを明示的に許可しています。

HTML meta タグでの CSP 設定

サーバー設定を変更できない環境では、index.html<head> セクションに meta タグを追加します。

html<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0"
    />

    <!-- CSP の設定 -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self';
                 script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;
                 style-src 'self' 'unsafe-inline';
                 img-src 'self' data: https:;"
    />

    <title>Vue.js アプリケーション</title>
  </head>
</html>

meta タグによる設定は手軽ですが、一部のディレクティブ(frame-ancestors など)が使えない制約があります。

Vue.js ビルド設定での CSP 対応

Vite を使用している場合、vite.config.js でビルド時の CSP 設定を調整できます。

javascript// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],

  // プロダクションビルド時の設定
  build: {
    // インラインスクリプトを避ける
    // CSP の 'unsafe-inline' を削減できます
    assetsInlineLimit: 0,

    // ソースマップは別ファイルに
    sourcemap: 'hidden',
  },
});

この設定により、インラインスクリプトの使用を最小限に抑え、より厳格な CSP ポリシーを適用できるようになります。

図で理解できる要点

  • CSP は複数のディレクティブでリソース種別ごとに読み込み元を制限します
  • 違反したリクエストは自動的にブロックされ、レポートとして記録できます
  • サーバー設定と HTML meta タグの 2 つの実装方法があります

SRI の実装例

SRI を適用するには、リソースのハッシュ値を計算し、HTML タグの integrity 属性に設定します。

CDN からの Vue.js 読み込みに SRI を適用

html<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js アプリ with SRI</title>

    <!-- Vue.js 3.x を CDN から読み込み、SRI で検証 -->
    <script
      src="https://cdn.jsdelivr.net/npm/vue@3.4.21/dist/vue.global.prod.js"
      integrity="sha384-bKbANK+fVqGDqZPh7WDG8h0F8z7xXcMVbRqTl5zEGqI3LbQ5QnIRRbZKg6F6WNvQ"
      crossorigin="anonymous"
    ></script>
  </head>
</html>

crossorigin="anonymous" 属性は、CORS リクエストとして扱うために必須です。これがないと SRI チェックが機能しません。

ハッシュ値の生成方法

コマンドラインでファイルのハッシュ値を計算する方法をご紹介します。

bash# ローカルファイルの SHA-384 ハッシュ値を計算
openssl dgst -sha384 -binary vue.global.prod.js | openssl base64 -A

出力されたハッシュ値を sha384- の形式で integrity 属性に指定します。

Webpack/Vite でのビルド時 SRI 自動生成

ビルドツールを使用している場合、プラグインで自動的に SRI を付与できます。

javascript// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { createHtmlPlugin } from 'vite-plugin-html';

export default defineConfig({
  plugins: [
    vue(),
    createHtmlPlugin({
      inject: {
        data: {
          // ビルド時に SRI 情報を注入
          injectScript:
            '<script src="main.js" integrity="sha384-..." crossorigin></script>',
        },
      },
    }),
  ],
});

より本格的な自動化には、webpack-subresource-integrity プラグインを使用すると便利です。

bash# パッケージのインストール
yarn add -D webpack-subresource-integrity
javascript// webpack.config.js
const {
  SubresourceIntegrityPlugin,
} = require('webpack-subresource-integrity');

module.exports = {
  output: {
    crossOriginLoading: 'anonymous',
  },
  plugins: [
    new SubresourceIntegrityPlugin({
      hashFuncNames: ['sha384'],
    }),
  ],
};

このプラグインを使うと、ビルド時に自動的にすべてのアセットファイルに SRI 属性が追加されます。

Cache-Control の実装例

適切なキャッシュ戦略を実装することで、ページ読み込み速度が劇的に向上します。

Nginx での Cache-Control 設定

リソースの種類ごとに異なるキャッシュポリシーを設定する例です。

nginxserver {
  listen 80;
  server_name example.com;
  root /var/www/dist;

  # index.html は常に最新版を取得
  location = /index.html {
    add_header Cache-Control "no-cache, must-revalidate";
    expires 0;
  }

  # JavaScript と CSS(ハッシュ付き)は長期キャッシュ
  location ~* \.(js|css)$ {
    add_header Cache-Control "public, max-age=31536000, immutable";
    expires 1y;
  }

この設定により、ハッシュ付きファイルは 1 年間キャッシュされ、エントリーポイントの index.html は毎回最新版がチェックされます。

画像とフォントのキャッシュ設定

nginx  # 画像ファイルは中期キャッシュ
  location ~* \.(png|jpg|jpeg|gif|svg|webp)$ {
    add_header Cache-Control "public, max-age=2592000";
    expires 30d;
  }

  # フォントファイルは長期キャッシュ
  location ~* \.(woff|woff2|ttf|otf|eot)$ {
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Access-Control-Allow-Origin "*";
    expires 1y;
  }
}

画像は更新される可能性があるため 30 日、フォントは変更頻度が低いため 1 年のキャッシュ期間を設定しています。

Vite でのビルド設定とキャッシュバスティング

Vite はデフォルトでファイル名にハッシュを付与しますが、より詳細な制御も可能です。

javascript// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],

  build: {
    rollupOptions: {
      output: {
        // ファイル名にハッシュを含める
        entryFileNames: 'assets/[name].[hash].js',
        chunkFileNames: 'assets/[name].[hash].js',
        assetFileNames: 'assets/[name].[hash].[ext]',
      },
    },

    // CSS コード分割
    cssCodeSplit: true,

    // マニフェストファイルを生成
    manifest: true,
  },
});

この設定でビルドすると、すべてのファイルにハッシュが付与され、ファイル内容が変更された場合のみファイル名が変わります。

Service Worker でのキャッシュ戦略

PWA として運用する場合、Service Worker を使ったより高度なキャッシュ制御も可能です。

javascript// service-worker.js

// キャッシュ名(バージョン管理用)
const CACHE_NAME = 'vue-app-v1.0.0';

// キャッシュするリソースのリスト
const STATIC_CACHE = [
  '/',
  '/index.html',
  '/assets/main.js',
  '/assets/main.css',
  '/assets/logo.png',
];

// インストール時:静的リソースをキャッシュ
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches
      .open(CACHE_NAME)
      .then((cache) => cache.addAll(STATIC_CACHE))
  );
});

Service Worker のフェッチイベントで、キャッシュ優先またはネットワーク優先の戦略を実装します。

javascript// フェッチ戦略:キャッシュファースト
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      // キャッシュにあればそれを返す
      if (response) {
        return response;
      }

      // なければネットワークから取得
      return fetch(event.request).then((response) => {
        // レスポンスをキャッシュに保存
        if (response.ok) {
          const responseClone = response.clone();
          caches
            .open(CACHE_NAME)
            .then((cache) =>
              cache.put(event.request, responseClone)
            );
        }
        return response;
      });
    })
  );
});

この実装により、オフライン時でもキャッシュされたリソースでアプリケーションが動作するようになります。

図で理解できる要点

  • ファイル種別によってキャッシュ期間を調整することが重要です
  • ハッシュ付きファイル名により、更新時に自動的に新しいファイルが取得されます
  • Service Worker を使うとオフライン対応も可能になります

エラーログ収集の実装例

本番環境でのエラーを確実に捕捉し、開発チームへ通知する仕組みを構築します。

Vue.js のグローバルエラーハンドラー設定

javascript// main.js
import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);

// Vue アプリケーション内のエラーをキャッチ
app.config.errorHandler = (err, instance, info) => {
  console.error('Vue エラー:', err);
  console.error('発生箇所:', info);

  // エラー情報を整形
  const errorData = {
    message: err.message,
    stack: err.stack,
    componentName: instance?.$options?.name || 'Unknown',
    lifecycle: info,
    timestamp: new Date().toISOString(),
    userAgent: navigator.userAgent,
    url: window.location.href,
  };

  // エラートラッキングサービスへ送信
  sendToErrorService(errorData);
};

app.mount('#app');

このハンドラーにより、Vue コンポーネント内で発生したすべてのエラーが捕捉されます。

ブラウザレベルのエラーハンドラー

Vue の管理外で発生するエラーにも対応します。

javascript// error-tracker.js

// 同期エラーのキャッチ
window.addEventListener('error', (event) => {
  const errorData = {
    type: 'JavaScript Error',
    message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    stack: event.error?.stack,
    timestamp: new Date().toISOString(),
  };

  sendToErrorService(errorData);
});

// Promise の未処理リジェクションをキャッチ
window.addEventListener('unhandledrejection', (event) => {
  const errorData = {
    type: 'Unhandled Promise Rejection',
    message: event.reason?.message || event.reason,
    stack: event.reason?.stack,
    timestamp: new Date().toISOString(),
  };

  sendToErrorService(errorData);
});

これにより、非同期処理のエラーも漏れなく収集できます。

Sentry との連携実装

Sentry は本番環境でのエラートラッキングに広く使われているサービスです。

bash# Sentry SDK のインストール
yarn add @sentry/vue
javascript// main.js
import { createApp } from 'vue';
import * as Sentry from '@sentry/vue';
import App from './App.vue';

const app = createApp(App);

// Sentry の初期化(本番環境のみ)
if (import.meta.env.PROD) {
  Sentry.init({
    app,
    dsn: 'https://your-sentry-dsn@sentry.io/project-id',

    // 環境情報
    environment: import.meta.env.MODE,

    // リリースバージョン
    release: 'vue-app@1.0.0',

    // サンプリングレート(100% = すべてのエラーを送信)
    tracesSampleRate: 1.0,

    // パフォーマンス監視
    integrations: [
      new Sentry.BrowserTracing({
        routingInstrumentation:
          Sentry.vueRouterInstrumentation(router),
      }),
    ],
  });
}

app.mount('#app');

Sentry を導入すると、エラーの自動収集、スタックトレース、ユーザー影響度、パフォーマンス指標などが管理画面で確認できます。

カスタムエラー送信関数の実装

外部サービスを使わず、自前のサーバーへエラーログを送信する場合の実装例です。

javascript// error-tracker.js

// エラー送信のキュー(オフライン時の対策)
const errorQueue = [];
let isOnline = navigator.onLine;

// オンライン状態の監視
window.addEventListener('online', () => {
  isOnline = true;
  flushErrorQueue();
});

window.addEventListener('offline', () => {
  isOnline = false;
});

// エラーをサーバーへ送信
async function sendToErrorService(errorData) {
  // ローカルストレージへ保存(デバッグ用)
  saveToLocalStorage(errorData);

  if (!isOnline) {
    // オフライン時はキューに追加
    errorQueue.push(errorData);
    return;
  }

  try {
    const response = await fetch(
      'https://api.example.com/errors',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(errorData),
      }
    );

    if (!response.ok) {
      console.error('エラー送信失敗:', response.status);
    }
  } catch (error) {
    console.error('エラー送信中の例外:', error);
    // 送信失敗時はキューに追加
    errorQueue.push(errorData);
  }
}

キューに溜まったエラーをまとめて送信する処理です。

javascript// キューのフラッシュ(再接続時に実行)
async function flushErrorQueue() {
  if (errorQueue.length === 0) return;

  const errors = [...errorQueue];
  errorQueue.length = 0;

  try {
    await fetch('https://api.example.com/errors/batch', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ errors }),
    });
  } catch (error) {
    // 再度失敗した場合はキューに戻す
    errorQueue.push(...errors);
  }
}

// ローカルストレージへの保存(開発時の確認用)
function saveToLocalStorage(errorData) {
  const key = `error_${Date.now()}`;
  const errors = JSON.parse(
    localStorage.getItem('errors') || '[]'
  );
  errors.push({ key, data: errorData });

  // 最新 50 件のみ保持
  if (errors.length > 50) {
    errors.shift();
  }

  localStorage.setItem('errors', JSON.stringify(errors));
}

export { sendToErrorService };

この実装により、オフライン時でもエラー情報が失われず、接続回復後に自動送信されます。

ユーザーコンテキスト情報の追加

エラーの原因究明には、エラー発生時のユーザー状態やアプリケーションの状態も重要です。

javascript// context-collector.js

// ユーザーコンテキスト情報を収集
function collectUserContext() {
  return {
    // ブラウザ情報
    userAgent: navigator.userAgent,
    language: navigator.language,
    platform: navigator.platform,

    // 画面情報
    screenResolution: `${window.screen.width}x${window.screen.height}`,
    viewportSize: `${window.innerWidth}x${window.innerHeight}`,

    // 現在のページ
    url: window.location.href,
    referrer: document.referrer,

    // タイムスタンプ
    timestamp: new Date().toISOString(),
    timezone:
      Intl.DateTimeFormat().resolvedOptions().timeZone,
  };
}

// アプリケーション状態を収集(Vue Router 使用時)
function collectAppContext(router, store) {
  return {
    currentRoute: router?.currentRoute?.value?.fullPath,
    routeName: router?.currentRoute?.value?.name,

    // Vuex/Pinia のストア状態(機密情報は除外)
    storeState: sanitizeState(store?.state),
  };
}

// 機密情報を除外する処理
function sanitizeState(state) {
  if (!state) return null;

  const sanitized = JSON.parse(JSON.stringify(state));

  // パスワードやトークンなどを削除
  const sensitiveKeys = [
    'password',
    'token',
    'apiKey',
    'secret',
  ];

  function removeSensitive(obj) {
    for (const key in obj) {
      if (
        sensitiveKeys.some((sk) =>
          key.toLowerCase().includes(sk)
        )
      ) {
        obj[key] = '[REDACTED]';
      } else if (
        typeof obj[key] === 'object' &&
        obj[key] !== null
      ) {
        removeSensitive(obj[key]);
      }
    }
  }

  removeSensitive(sanitized);
  return sanitized;
}

export { collectUserContext, collectAppContext };

これらのコンテキスト情報をエラーデータに含めることで、問題の再現と解決が容易になります。

以下の図は、エラー発生から通知までの全体フローを示しています。

mermaidflowchart TB
    error["エラー発生"] --> vueHandler["Vue エラー<br/>ハンドラー"]
    error --> windowHandler["Window エラー<br/>ハンドラー"]

    vueHandler --> collect["コンテキスト<br/>情報収集"]
    windowHandler --> collect

    collect --> check{"オンライン?"}
    check -->|Yes| send["即座に送信"]
    check -->|No| queue["キューに保存"]

    send --> service["エラー<br/>サービス"]
    queue --> reconnect["再接続時"]
    reconnect --> batch["バッチ送信"]
    batch --> service

    service --> notify["開発チーム<br/>通知"]

図で理解できる要点

  • Vue アプリ内外のエラーを包括的に捕捉する多層構造が重要です
  • オフライン時にもエラー情報を保持し、復帰後に送信する仕組みが必要です
  • ユーザーコンテキスト情報を含めることで、問題の再現性が向上します

まとめ

本記事では、Vue.js アプリケーションを本番環境で安全かつ高速に運用するための 4 つの重要な要素について解説しました。

CSP(Content Security Policy)を適切に設定することで、XSS 攻撃のリスクを大幅に軽減し、想定外のリソース読み込みを防ぐことができます。サーバー設定または HTML meta タグで実装でき、違反内容をレポートとして収集することも可能です。

SRI(Subresource Integrity)は、CDN から読み込む外部リソースの改ざんを検知する仕組みです。特に Vue.js 本体や UI ライブラリを外部から読み込む場合、integrity 属性を設定することでセキュリティが向上します。ビルドツールのプラグインを使えば、自動的に SRI を付与することもできます。

Cache-Control ヘッダーを適切に設定することで、ページ読み込み速度が劇的に改善します。エントリーポイントの index.html はキャッシュせず、ハッシュ付きの JavaScript や CSS ファイルは長期キャッシュするという戦略が効果的です。Nginx や Apache での設定例、Service Worker を使った高度なキャッシュ制御についてもご紹介しました。

エラーログ収集の仕組みを構築することで、本番環境で発生した問題を迅速に検知し対処できます。Vue のグローバルエラーハンドラーとブラウザレベルのエラーハンドラーを組み合わせ、Sentry などの外部サービスまたは自前のサーバーへエラー情報を送信する実装方法を解説しました。

これら 4 つの要素は、それぞれ独立して効果を発揮しますが、すべてを組み合わせることで、より堅牢で高速な Vue.js アプリケーションを実現できます。本番環境へのデプロイ前に、ぜひこのチェックリストを確認してみてください。

関連リンク