はじめに
PWA(Progressive Web Apps)は、Webサイトにネイティブアプリのような機能や使い勝手を付加する技術です。
Webサイトでありながら、
- ホーム画面にアイコンを「インストール」できる
- オフラインでも(ある程度)動作する
- スプラッシュスクリーン(起動画面)を表示できる
といった特徴を持たせることができます。
特にiPhone、Androidのスマホにおいて、App StoreやGoogle Playに公開せずとも、あたかもアプリのように扱える のが魅力です。
この記事では、PWAの概要と、既存のWebサイトをPWA化する具体的な実装方法について解説します。
PWAの例
PWAがどのようなものか、イメージを掴んでいただくために、PWAに対応しているQiitaを例に見てみましょう。
1. Chrome (デスクトップ版)
ChromeでQiitaのサイトを開くと、URLバーの右端にインストールするようなアイコンが表示されるのでクリックします。
ポップアップが出るので 「インストール」 を押します。
これでアプリとしてインストールされ、Windowsのタスクバーや、Macのドックに固定できます。
起動すると、ブラウザのアドレスバーなどがない、スッキリとしたウィンドウで開きます。
(実態はアドレスバーやメニューが非表示になっているChromeです)
2. iPhone (Safari)
iPhoneの場合、Safariの 「共有」 アイコンをタップします。
「ホーム画面に追加」 を選択します。
ホーム画面にアイコンが追加され、他のアプリと同じように表示されます。
ここから起動すると、Qiitaがアプリのように振る舞います。
(実態はアドレスバーやメニューが非表示になっているSafariです)
3. Android (Chrome)
AndroidのChromeでは、PWA対応サイトにアクセスすると「ホーム画面に追加」のバナーが自動的に表示されることがあります。
また、バナーが出ない場合でも、メニューから 「ホーム画面に追加」 を選択します。
インストール を選択します。
ホーム画面にアイコンが追加され、他のアプリと同じように表示されます。
ここから起動すると、iPhone同様にQiitaがアプリのように振る舞います。
(実態はアドレスバーやメニューが非表示になっているChromeです)
実装方法の解説
それでは、具体的な実装方法を解説します。
今回は、私がバイブコーディングで作成した、こども向けプログラミング学習アプリをプレイできるウェブサイト をPWA化する手順で説明します。
- 対象サイトURL: https://hamham.dev/education/index.html
PWAを実装するために必要な作業は、大きく分けて以下の3つです。
-
Web App Manifest (
manifest.json) の作成 -
Service Worker (
service-worker.js) の作成 -
HTML (
index.html) の編集
※前提として、PWA化にはHTTPSでサイトが配信 されている必要があります。
1. Web App Manifest (manifest.json) を作成する
Web App Manifestは、Webサイトをアプリとしてインストールする際の情報を定義するJSONファイルです。
index.html と同じディレクトリ(/education/)に manifest.json という名前でファイルを作成し、以下のように記述します。
{
"name": "ろじかるクエスト - こども向けプログラミング学習アプリ",
"short_name": "ろじかるクエスト",
"description": "楽しみながらプログラミング的思考を育む学習アプリ",
"start_url": "/education/index.html",
"display": "standalone",
"background_color": "#f0f9ff",
"theme_color": "#6366f1",
"orientation": "any",
"icons": [
{
"src": "./icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "./icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
主な項目の解説
-
name: アプリの正式名称。インストール時の確認ダイアログなどで使用されます。 -
short_name: ホーム画面のアイコン下に表示される短い名前です。 -
description:アプリの説明文です。ユーザーにアプリの概要を伝えるために使用されます。 -
start_url: アプリ起動時に最初に開くURL。サイトのルートから指定しています。 -
display: 表示モード。standaloneにすると、アドレスバーなどを非表示にし、アプリらしく表示します。 -
background_color: スプラッシュスクリーン(起動画面)の背景色です。アプリが起動するまでの間、この背景色とiconsで指定したアイコンが表示されます。 -
theme_color: アプリのツールバーなどの色を指定します。 -
orientation:アプリの画面の向きを指定します。anyは、端末の向き(縦向き・横向き)に合わせて回転を許可する設定です。他にもportrait(縦向き固定)やlandscape(横向き固定)などを指定できます。 -
icons: ホーム画面やスプラッシュスクリーンで使われるアイコン。HTML指定のicon-192.pngと、PWAで推奨されるicon-512.pngを指定します。-
src:アイコン画像のパスです。manifest.jsonからの相対パスで指定します。 -
sizes:アイコンのサイズ(幅x高さ)をピクセル単位で指定します。 -
type:画像のMIMEタイプを指定します。 -
purpose:any maskableでアイコンがアプリアイコンの形状(円形や四角丸など)に合わせて切り抜かれることに対応していることを示します。PWAでは指定が推奨されています。
-
2. Service Worker (service-worker.js) を作成する
Service Workerは、オフライン対応などのためにブラウザのバックグラウンドで動くスクリプトです。
index.html と同じ階層に service-worker.js という名前でファイルを作成します。
const CACHE_NAME = 'logical-quest-v1';
// キャッシュするファイルのリスト
// service-worker.jsファイルからの相対パスで指定
const urlsToCache = [
'./index.html',
'./3d_route.html',
'./jigsaw_puzzle.html',
'./route_puzzle.html'
// アイコンやCSSなども必要に応じて追加
];
// 1. インストールイベント (Service Worker登録時に実行)
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('Opened cache');
// 指定されたファイルをすべてキャッシュする
return cache.addAll(urlsToCache);
})
.catch((error) => {
console.log('Cache installation failed:', error);
})
);
});
// 2. フェッチイベント (リクエスト送信時に実行) - キャッシュファースト
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
// キャッシュにあればそれを返し、なければネットワークから取得
return response || fetch(event.request);
})
);
});
// 3. アクティベートイベント (Service Workerが有効になった時に実行)
self.addEventListener('activate', (event) => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
// CACHE_NAME (v1) 以外の古いキャッシュ(v0など)を削除する
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
このコードは、インストール時に urlsToCache に指定されたHTMLファイルをキャッシュします。
fetch時に、まずキャッシュを探し、あればキャッシュを返します。なければネットワークにリクエストします(キャッシュファースト戦略)。
activate時に、CACHE_NAME で指定された名前以外の古いキャッシュを自動で削除します。これにより、キャッシュ名を v2 に更新した際に古い v1 をクリーンアップできます。(アプリを更新する上で重要です)
3. HTML (index.html) を編集する
最後に、index.html を編集して、作成した2つのファイルを読み込みます。
① Manifestのリンク
最後に、index.html が、作成した2つのファイルを読み込むようになっているか確認します。
<head> タグ内に、manifest.json へのリンクが記述されていることを確認します。
<head>
<link rel="manifest" href="manifest.json">
</head>
② Service Workerの登録
<body> タグの閉じ直前などに、service-worker.js を登録するスクリプトを追加されていることを確認します。
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
// 'service-worker.js' を登録 (index.htmlからの相対パス)
navigator.serviceWorker.register('service-worker.js')
.then((registration) => {
console.log('Service Worker registered successfully:', registration.scope);
})
.catch((error) => {
console.log('Service Worker registration failed:', error);
});
});
}
</script>
③ iOS用設定をする
manifest.json はGoogleが主導するPWAの標準規格であり、ChromeやEdgeではうまく機能します。
しかしながら、Safariは manifest.jsonを完全にはサポートしていません。
そのためindex.htmlにapple-で始まるメタタグを入れておくのがベストです。
<head>
<link rel="apple-touch-icon" href="icon-192.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="ろじかるクエスト">
</head>
主な項目の解説
-
apple-touch-icon: 「ホーム画面に追加」したときに表示される、アプリアイコンを指定します。これを指定しないと、Webページのスクリーンショットを縮小したものをアイコンとして使用してしまいます。 -
apple-mobile-web-app-capable:yesに設定すると、iPhoneやiPadで「ホーム画面に追加」されたアイコンからアプリを起動した際に、SafariのアドレスバーやフッターUIが非表示になり、ネイティブアプリのように全画面(スタンドアロンモード)で動作します。 -
apple-mobile-web-app-status-bar-style:画面上部のステータスバーのスタイルを指定します。defaultは通常のステータスバーの配色になります。 -
apple-mobile-web-app-title: ホーム画面に追加されたアイコンの下に表示される名前を指定します。
PWAの動作確認
ここではiPadを使ってみていきます。
まずSafariで「ホーム画面に追加」を行います。
ホーム画面にアイコンが表示されたのでタップすると、アドレスバーやメニューボタンが非表示でアプリが起動します。
1度HTMLファイルを読み込めば、キャッシュが効くためオフラインでもプレイすることが可能です(下記はWifiを切っています)
さいごに
今回は、基本的なPWAの実装方法を紹介しました。
特にService Workerのキャッシュ戦略は非常に奥が深く、今回はシンプルにキャッシュを優先する Cache First で設定しましたが、以下のような設計・設定もあります。
- Network First :常にネットワークを優先し、オフライン時だけキャッシュを使う
- Stale-While-Revalidate :キャッシュを返しつつ、裏側で新しい情報を取得しにいく
PWA化の第一歩として、この記事が参考になれば幸いです。











