Vite + Reactのサイト(SPA)をPWA化する手順(vite-plugin-pwa)

概要

今回はVite + ReactのSPAをvite-plugin-pwaでPWA化する手順を紹介していきます。

PWA化するとホーム画面追加・オフライン動作・LighthouseのPWAスコア獲得と、個人開発でも嬉しい恩恵がまとめて手に入ります。
個人的にはブラウザでの表示と違い、画面上にURLが表示されないことがかなりうれしい!
AppStoreからインストールしたアプリみたいに動くのは開発者としてうれしいですよ(^^

実際に自分が作ったcopype.shinpinoshi.comこぴぺったり(ローカルストレージで動くコピペ管理アプリ)でもこの手順そのままでPWA化していて、スマホからホーム画面に追加するとアプリのように起動できます。

それではやっていきましょう(^^!

目次

そもそもPWA / SPA / Service Workerとは(用語整理)

「PWA化したSPA」と聞いてもパッと意味が掴みにくいので、先に登場する単語を整理しておきます。

  • SPA (Single Page Application): 1枚のHTMLを最初に読み込んだあと、JavaScriptがルーティングや画面更新を担当するWebアプリ形式。React Routerで/ /about /settingsを切り替えるアレ
  • PWA (Progressive Web App): manifest.webmanifestとService Workerを備えていて、スマホのホーム画面に「インストール」できたり、オフラインでも動くWebアプリ
  • Service Worker: ブラウザのバックグラウンドで動くJSワーカー。ページとネットワークの間に割り込んで、キャッシュやオフライン制御をやってくれる仕組み

つまり「PWA化したSPA」とは、SPAにmanifestとService Workerを足して、ホーム追加とオフライン対応を持たせた状態のこと。
要はみんながイメージするスマホアプリそのもののように動くということです!

タイトルにあるvite-plugin-pwaはこの「manifest + Service Worker」をViteのビルドに統合してくれるプラグインです。

全体像(4ステップ)

PWA化は次の4ステップで完了します。

  • ① vite-plugin-pwaを導入: npm iしてプラグインをvite.config.jsに追加
  • ② manifestを整える: アプリ名・アイコン・テーマ色など、ホーム追加されたときの見た目を決める
  • ③ Service Workerのキャッシュ戦略を決める: precache + runtimeCachingでオフライン対応
  • ④ Lighthouseとインストール挙動で動作確認: 本番ビルドして実際にスコアと挙動を確認

めっちゃ簡単ですよね(^^/
さっそく順にやっていきましょう。

① vite-plugin-pwaを入れて最小設定で動かす

まずプラグインをインストールします。

コマンド

1
npm i -D vite-plugin-pwa

インストール後、vite.config.jsに設定を追加します。

vite.config.js

1
2
3
4
5
6
7
8
9
10
11
12
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { VitePWA } from "vite-plugin-pwa";

export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: "autoUpdate",
}),
],
});

これだけでビルド時にService Workerが生成され、index.htmlに登録スクリプトが差し込まれる状態になります。

registerTypeには2種類あります。

  • autoUpdate: 新しいService Workerが出たら自動で適用(次回リロード時)。シンプルで個人開発向き
  • prompt: 「新しいバージョンがあります」というUIをユーザーに出して手動で更新させる。業務系で更新タイミングを制御したいとき向き

特にこだわりがなければautoUpdateでOKです!

ちなみにdevOptions.enabledfalse(既定)にしている場合、npm run dev中はService Workerが走りません(- -
PWA挙動の確認はnpm run build && npm run previewで行うのが安全です。

② manifestを整える(アプリ名・アイコン・テーマ色)

manifestはホーム画面に追加されたときの「アプリの顔」としてアプリ名やアイコン、テーマ色などを決める場所です。
vite-plugin-pwaならインラインでオブジェクトを書けます。

vite.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
VitePWA({
registerType: "autoUpdate",
includeAssets: ["favicon.png", "apple-touch-icon.png"],
manifest: {
name: "こぴぺったり",
short_name: "こぴぺったり",
description: "登録不要、ブラウザだけで動く定型文ワンタップコピーアプリ。",
lang: "ja",
start_url: "/",
scope: "/",
display: "standalone",
orientation: "any",
theme_color: "#16131f",
background_color: "#16131f",
icons: [
{ src: "pwa-192x192.png", sizes: "192x192", type: "image/png", purpose: "any" },
{ src: "pwa-512x512.png", sizes: "512x512", type: "image/png", purpose: "any" },
{ src: "pwa-maskable-512x512.png", sizes: "512x512", type: "image/png", purpose: "maskable" },
],
},
}),

各キーの意味はこんな感じ。

  • name / short_name
    • ホーム画面やアプリ一覧に出る名称。short_nameは12文字以内が目安
  • start_url / scope
    • アプリ起動時に開くURLと、Service Workerが制御する範囲
  • display: standalone
    • ブラウザのアドレスバーを隠してネイティブアプリ風に起動
  • theme_color / background_color
    • アプリ起動時のスプラッシュ画面・タイトルバー色
  • icons
    • any(通常用)とmaskable(角丸を被せても潰れないセーフゾーン版)の両方を用意

アイコン画像はpublic/直下に置いておけば、ビルド時にdist/ルートへコピーされます。
サイズは192×192と512×512、それにmaskable用の512×512の3枚あればOK!

maskableアイコンを用意しないと、Androidのホーム画面で白い枠が出たり、中身が切れて見えたりします。
ちょっと面倒ですが、セーフゾーン(中央80%に主要要素を収める)を意識して、アイコンの主要要素はそこに収めましょう。

③ Service Workerのキャッシュ戦略(オフライン対応)

vite-plugin-pwaは内部でWorkboxを使ってService Workerを生成します。
workboxキーで挙動を細かく制御できます。

vite.config.js

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
VitePWA({
// ...略
workbox: {
globPatterns: ["**/*.{js,css,html,svg,png,ico,webmanifest}"],
cleanupOutdatedCaches: true,
runtimeCaching: [
{
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
handler: "StaleWhileRevalidate",
options: {
cacheName: "google-fonts-stylesheets",
expiration: { maxEntries: 10, maxAgeSeconds: 60 * 60 * 24 * 365 },
},
},
{
urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
handler: "CacheFirst",
options: {
cacheName: "google-fonts-webfonts",
expiration: { maxEntries: 30, maxAgeSeconds: 60 * 60 * 24 * 365 },
cacheableResponse: { statuses: [0, 200] },
},
},
],
},
}),

ポイントは3つ。

  • globPatterns
    • ビルド成果物のうちプリキャッシュ(インストール時に一括キャッシュ)する対象
  • cleanupOutdatedCaches: true
    • 古いバージョンのService Workerが残したキャッシュを自動削除
  • runtimeCaching
    • 外部ドメイン(Google Fontsなど)など、ビルド時に分からないリソースの戦略

上の例はGoogle Fontsの戦略をリソース別に分けています。

  • stylesheets (CSS) → StaleWhileRevalidate(古くてもまずキャッシュを返して、裏で更新)
  • webfonts (woff2) → CacheFirst(フォント実体はほぼ変化しないので長期キャッシュ向き)

戦略選びの目安はこんな感じ。

  • CacheFirst: 不変アセット(フォント・ロゴ画像など)。最速・ネットワーク不要
  • StaleWhileRevalidate: たまに更新(CSS・スタイル・OGP画像など)。体感は速いまま裏で更新
  • NetworkFirst: 鮮度優先(API・記事一覧・通知など)。オフライン時はキャッシュにフォールバック

こぴぺったりはツールなので、NetworkFirstは使用してませんが、必要に応じて使用したほうが良いかもしれません!
ローカルストレージ完結のSPAなら、precache + Google FontsのruntimeCachingだけで十分にオフライン動作します(^^

④ Lighthouseとインストール挙動で確認する

設定が終わったら本番ビルドして検証します。

  • npm run build && npm run previewで本番ビルドをローカル配信
  • Chrome DevTools → Lighthouseタブ → Categoriesで「Progressive Web App」を選んで実行
  • DevTools → ApplicationManifestでアイコン・色がプレビューどおりか目視
  • DevTools → ApplicationService WorkersにアクティブなSWが登録されているか確認

インストール挙動も実機で確認します。

  • PCのChrome: アドレスバー右端に「インストール」アイコン(モニタに⤓マーク)が出る
  • Androidスマホ: メニューから「ホーム画面に追加」、または自動で出るインストールバナー
  • iOS Safari: 共有メニュー → 「ホーム画面に追加」

npm run devのdevサーバではService Workerが動かないので、PWA挙動の検証はpreviewか本番ドメインで行いましょう。

沼ったところ

古いService Workerが残ってキャッシュが効かない

設定を更新したのに反映されない場合は、古いService Workerがブラウザに残っていることが多いです。

  • Chrome DevTools → Application → Service Workers → Unregister
  • もしくはApplication → Storage → Clear site data
  • registerType: "autoUpdate"でも、適用にはリロード2回必要なケースがあります

maskableアイコンを忘れてAndroidで白枠

pwa-maskable-512x512.pngのようなpurpose: "maskable"指定の512×512を1枚追加すれば解消します。

セーフゾーン(中央80%に主要要素を収める)を意識して作ると、円形・角丸・水滴型などどんなマスクをかけられても潰れません。

devサーバでPWA挙動が出ない

devOptions.enabled: false(既定)のとき、npm run dev中はService Workerが登録されません。
これは仕様だそうです。

PWA挙動の確認はnpm run build && npm run previewで行うようにしましょう!
devでPWAも触りたい場合だけdevOptions.enabled: trueにします。

締め

いかがでしたでしょうか?
これをやると結構いい感じに実装できちゃいます。

ここで紹介したことをサクッとまとめると

  • vite-plugin-pwaを入れてregisterTypeだけ決めれば、最低限のPWA化は完了
  • manifestはホーム追加の見え方を決める設定。アイコンはany + maskableの両対応が必須
  • Service WorkerはWorkboxのruntimeCachingでリソース別に戦略を分ける
  • 確認は本番ビルド + Lighthouse + 実機ホーム追加
といった感じです。

こんな感じで実装したのが、下記のコピー&ペーストを簡単にするツールです。
良ければ使ってみてください!

copype.shinpinoshi.comこぴぺったり

以上となります。
審査が無くても簡単に上げれるのは便利でよいですね!
それではお疲れさまでした!