1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vite + ReactでService Workerを使ってオフライン対応する

Posted at

はじめに

Webアプリをオフラインでも動くようにしたいなと思ったんですが、Service Workerについてあまり理解できていなかったので改めて勉強してまとめました。

Vite + Reactの環境だとvite-plugin-pwaを使うと案外簡単にできます。

やりたいこと

  • Vite + Reactアプリをオフラインでも動作させる
  • Service Workerでアセットをキャッシュする
  • PWAとしてインストール可能にする
  • SPAのルーティングをオフラインでも動作させる

環境

  • Vite 7.x
  • React 18.x
  • TypeScript
  • vite-plugin-pwa 1.2.0

手順

1. ライブラリのインストール

npm install -D vite-plugin-pwa

2. vite.config.tsの設定

vite.config.ts
// ① vite-plugin-pwaをインポート
import { VitePWA } from 'vite-plugin-pwa'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    react(),
    // ② VitePWAプラグインを追加
    VitePWA({
      // ③ 自動更新モードを指定
      registerType: 'autoUpdate',
      // ④ キャッシュするファイルを指定
      includeAssets: ['favicon.ico', 'robots.txt', '**/*.svg', '**/*.png'],
      // ⑤ マニフェストの設定
      manifest: {
        name: 'My App',
        short_name: 'MyApp',
        description: 'My Awesome App',
        theme_color: '#ffffff',
        background_color: '#ffffff',
        display: 'standalone',
        start_url: '/',
        icons: [
          {
            src: 'pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: 'pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'maskable'
          }
        ]
      },
      // ⑥ Workboxの設定
      workbox: {
        globPatterns: ['**/*.{js,css,html,ico,png,svg,woff,woff2}'],
        // ⑦ クエリパラメータを無視してキャッシュマッチング
        ignoreURLParametersMatching: [/^v$/],
        // ⑧ SPAのナビゲーションフォールバック
        navigateFallback: 'index.html',
        navigateFallbackAllowlist: [/.*/],
        navigateFallbackDenylist: [/^\/api\//, /\.\w+$/]
      },
      // ⑨ 開発時の設定
      devOptions: {
        enabled: true,
        type: 'module',
        navigateFallback: 'index.html',
        navigateFallbackAllowlist: [/^\/(?!api).*$/]
      }
    })
  ]
})
番号 説明
vite-plugin-pwaからVitePWAをインポート
pluginsにVitePWA()を追加
registerType: 'autoUpdate'で新しいService Workerを自動適用
includeAssetsでキャッシュに含めるファイルを指定
manifestでPWAのメタ情報を設定
workbox.globPatternsでキャッシュ対象のファイルパターンを指定
バージョン番号などのクエリパラメータを無視
SPAルーティング用のナビゲーションフォールバック設定
開発時もService Workerを有効化

3. Service Workerの登録確認

開発時は確認しづらいので、ビルドしてプレビューで確認します。

npm run build && npm run preview

ブラウザのDevTools → Application → Service Workers で登録されているか確認できます。

4. オフラインで動作確認

DevToolsのNetworkタブで「Offline」にチェックを入れてリロードしてみてください。

ちゃんとキャッシュされていればページが表示されるはずです。

SPAルーティングのオフライン対応

SPAでは/users/123のようなURLに直接アクセスしたとき、サーバーがindex.htmlを返す必要があります。
オフライン時もこれを実現するために、ナビゲーションフォールバックの設定が必要です。

navigateFallbackの設定

vite.config.ts
workbox: {
  // オフライン時のナビゲーションフォールバック
  navigateFallback: 'index.html',
  // すべてのパスでフォールバックを許可
  navigateFallbackAllowlist: [/.*/],
  // APIやファイルはフォールバック対象外
  navigateFallbackDenylist: [/^\/api\//, /\.\w+$/]
}
設定 説明
navigateFallback フォールバック先のファイル
navigateFallbackAllowlist フォールバックを許可するパスの正規表現
navigateFallbackDenylist フォールバックを除外するパスの正規表現

開発モードでの注意点

開発モードではdevOptions.navigateFallbackAllowlistを別途設定する必要があります。
workbox.navigateFallbackAllowlistは開発モードには反映されません。

vite.config.ts
devOptions: {
  enabled: true,
  type: 'module',
  navigateFallback: 'index.html',
  // 開発モード専用のallowlist設定
  navigateFallbackAllowlist: [/^\/(?!api).*$/]
}

注意点

開発時はService Workerが完全には動かない

開発モード(npm run dev)では@vite/clientなどの開発専用ファイルがキャッシュされないため、完全なオフライン動作は確認できません。

オフライン動作を確認するには本番ビルドで確認しましょう。

npm run build && npm run preview

キャッシュの更新

registerType: 'autoUpdate'にしておくと、新しいバージョンがあれば自動で更新されます。

ユーザーに更新を促したい場合はregisterType: 'prompt'にして、更新UIを自前で実装する必要があります。

promptモードの実装例
src/App.tsx
import { useRegisterSW } from 'virtual:pwa-register/react'

function App() {
  const {
    needRefresh: [needRefresh, setNeedRefresh],
    updateServiceWorker,
  } = useRegisterSW()

  return (
    <>
      {needRefresh && (
        <div>
          <span>新しいバージョンがあります</span>
          <button onClick={() => updateServiceWorker(true)}>
            更新する
          </button>
        </div>
      )}
    </>
  )
}

APIリクエストのキャッシュ

デフォルトではAPIリクエストはキャッシュされません。
動的なデータもキャッシュしたい場合はWorkboxのruntimeCachingを設定します。

vite.config.ts
workbox: {
  globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
  runtimeCaching: [
    {
      urlPattern: /^https:\/\/api\.example\.com\/.*/i,
      handler: 'NetworkFirst',
      options: {
        cacheName: 'api-cache',
        expiration: {
          maxEntries: 50,
          maxAgeSeconds: 60 * 60 * 24 // 1日
        },
        cacheableResponse: {
          statuses: [0, 200]
        },
        networkTimeoutSeconds: 5
      }
    }
  ]
}
handler 説明 用途
NetworkFirst ネットワーク優先、失敗時にキャッシュ 最新データが重要なAPI
CacheFirst キャッシュ優先、なければネットワーク 変更頻度が低いアセット
StaleWhileRevalidate キャッシュを返しつつ、裏でネットワーク更新 頻繁に更新されるが即時性は不要なデータ

大きなファイルのキャッシュ(GLB、FBX、動画など)

3Dモデルや動画などの大きなファイルもruntimeCachingで対応できます。

vite.config.ts
runtimeCaching: [
  {
    // GLBファイル(3Dモデル)
    urlPattern: /^https?:\/\/[^/]+\/.*\.glb(\?.*)?$/i,
    handler: 'CacheFirst',
    options: {
      cacheName: 'glb-cache',
      expiration: {
        maxEntries: 50,
        maxAgeSeconds: 60 * 60 * 24 * 30 // 30日
      },
      cacheableResponse: {
        statuses: [0, 200]
      }
    }
  },
  {
    // 動画ファイル
    urlPattern: /^https?:\/\/[^/]+\/movies\/.*/,
    handler: 'CacheFirst',
    options: {
      cacheName: 'movies-cache',
      expiration: {
        maxEntries: 100,
        maxAgeSeconds: 60 * 60 * 24 * 30
      },
      cacheableResponse: {
        statuses: [0, 200]
      },
      // Range Requestsに対応(動画のシーク操作)
      rangeRequests: true
    }
  }
]

まとめ

vite-plugin-pwaを使うと、Vite + ReactでのService Worker導入が思ったより簡単にできました。

SPAのルーティング対応はnavigateFallback系の設定が必要で、特に開発モードではdevOptionsに別途設定が必要な点は注意が必要でした。

改めて勉強すると、Service Workerの仕組みも理解できてよかったです。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?