はじめに
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-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の設定
workbox: {
// オフライン時のナビゲーションフォールバック
navigateFallback: 'index.html',
// すべてのパスでフォールバックを許可
navigateFallbackAllowlist: [/.*/],
// APIやファイルはフォールバック対象外
navigateFallbackDenylist: [/^\/api\//, /\.\w+$/]
}
| 設定 | 説明 |
|---|---|
| navigateFallback | フォールバック先のファイル |
| navigateFallbackAllowlist | フォールバックを許可するパスの正規表現 |
| navigateFallbackDenylist | フォールバックを除外するパスの正規表現 |
開発モードでの注意点
開発モードではdevOptions.navigateFallbackAllowlistを別途設定する必要があります。
workbox.navigateFallbackAllowlistは開発モードには反映されません。
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モードの実装例
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を設定します。
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で対応できます。
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の仕組みも理解できてよかったです。
参考