MeWrite Docs

TypeError: Failed to fetch dynamically imported module

Viteでデプロイ後に動的インポートが失敗するエラー

概要

Viteでビルドしたアプリケーションをデプロイした後、ユーザーがページを開いたままの状態で新しいデプロイが行われると発生するエラーです。動的インポートされるチャンクファイルのハッシュが変わり、古いファイルが存在しなくなることが原因です。

エラーメッセージ

TypeError: Failed to fetch dynamically imported module: https://example.com/assets/Page-abc123.js

原因

  1. デプロイによるファイルハッシュ変更: Viteはコンテンツベースのハッシュをファイル名に付与するため、コード変更時にファイル名が変わる
  2. 古いチャンクファイルの削除: 新しいデプロイで古いチャンクファイルが削除される
  3. ブラウザキャッシュ: クライアントが古いマニフェストを保持している
  4. 動的インポートのタイミング: ユーザーがページ遷移時に存在しないファイルをリクエスト

解決策

1. エラーバウンダリでリロードを促す(React)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// ErrorBoundary.jsx
import { Component } from 'react';

class ChunkErrorBoundary extends Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    if (error.message.includes('Failed to fetch dynamically imported module')) {
      return { hasError: true };
    }
    throw error;
  }

  handleReload = () => {
    window.location.reload();
  };

  render() {
    if (this.state.hasError) {
      return (
        <div>
          <p>新しいバージョンが利用可能です</p>
          <button onClick={this.handleReload}>再読み込み</button>
        </div>
      );
    }
    return this.props.children;
  }
}

2. 動的インポートをリトライする

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// utils/lazyWithRetry.js
export function lazyWithRetry(componentImport) {
  return React.lazy(async () => {
    try {
      return await componentImport();
    } catch (error) {
      if (error.message.includes('Failed to fetch dynamically imported module')) {
        // キャッシュをクリアしてリロード
        window.location.reload();
      }
      throw error;
    }
  });
}

// 使用例
const Dashboard = lazyWithRetry(() => import('./pages/Dashboard'));

3. Service Workerでキャッシュ管理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// sw.js
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames.map((cacheName) => {
          // 古いキャッシュを削除
          return caches.delete(cacheName);
        })
      );
    })
  );
});

4. バージョンチェックを実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// versionCheck.js
const APP_VERSION = import.meta.env.VITE_APP_VERSION;

async function checkVersion() {
  const response = await fetch('/version.json');
  const { version } = await response.json();

  if (version !== APP_VERSION) {
    // 新しいバージョンが利用可能
    if (confirm('新しいバージョンが利用可能です。更新しますか?')) {
      window.location.reload();
    }
  }
}

// 定期的にチェック
setInterval(checkVersion, 60000);

5. 古いチャンクを一定期間保持

1
2
3
4
# デプロイスクリプト例
# 古いファイルを即削除せず、一定期間保持
aws s3 sync ./dist s3://my-bucket --delete --exclude "*.js" --exclude "*.css"
aws s3 sync ./dist s3://my-bucket --include "*.js" --include "*.css"

6. Vue Routerでのエラーハンドリング

1
2
3
4
5
6
// router/index.js
router.onError((error) => {
  if (error.message.includes('Failed to fetch dynamically imported module')) {
    window.location.reload();
  }
});

よくある間違い

  • エラーを無視してユーザーに白い画面を見せてしまう
  • リロードループを引き起こす実装(リトライ回数を制限すべき)
  • すべての動的インポートエラーを同じ方法で処理(ネットワークエラーと区別すべき)

関連エラー

参考リンク

JavaScript の他のエラー

最終更新: 2025-12-13