【最小構成】PWAをオフライン対応にする手順(manifest + Service Worker だけで動かす)
はじめに
PWA(Progressive Web App)は、Webサイトをスマートフォンのホーム画面に追加して、アプリのように使えるようにする仕組みです。アプリストアの審査が要らず、Webの技術だけで作れます。
ライブラリを入れる前に、まず「何が最低限必要なのか」を押さえておくと、あとでトラブルが起きたときに原因を追いやすくなります。この記事では、manifest と Service Worker の2つだけで、ホーム画面に追加でき、オフラインでも開けるところまでを作ります。
対象読者
- HTML / CSS / JavaScript の基本が分かる方
- PWAの仕組みを、フレームワークなしで一度確認しておきたい方
完成イメージ
- スマホのホーム画面にアイコンを追加できる
- 追加したアイコンから、ブラウザのバーなしで起動できる
- 通信を切っても、表示済みのページが開ける
PWAに必要なもの
| 必要なもの | 役割 |
|---|---|
| HTTPS | Service Worker はHTTPSでのみ動く(localhost は例外) |
manifest.json |
アプリ名・アイコン・表示方法を伝える |
| Service Worker | キャッシュを管理し、オフライン時の応答を返す |
ファイル構成
/
├─ index.html
├─ offline.html
├─ manifest.json
├─ sw.js
└─ icons/
├─ icon-192.png
└─ icon-512.png
アイコンは 192×192 と 512×512 の PNG を用意します。
1. manifest.json を作る
{
"name": "サンプルPWA",
"short_name": "サンプル",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0e7c86",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}
-
display: "standalone"にすると、ブラウザのアドレスバーが消えてアプリらしい見た目になります。 -
start_urlは、ホーム画面のアイコンから開いたときの最初のページです。
2. index.html で読み込む
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>サンプルPWA</title>
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#0e7c86">
<link rel="apple-touch-icon" href="/icons/icon-192.png">
</head>
<body>
<h1>サンプルPWA</h1>
<p>オフラインでも開けます。</p>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then((reg) => console.log('SW registered:', reg.scope))
.catch((err) => console.error('SW registration failed:', err));
});
}
</script>
</body>
</html>
iOS(Safari)では、apple-touch-icon を指定しておくとホーム画面のアイコンに使われます。
3. offline.html を用意する
通信できず、キャッシュにもないページを開いたときに出す画面です。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>オフライン</title>
</head>
<body>
<h1>オフラインです</h1>
<p>通信が回復したら、もう一度お試しください。</p>
</body>
</html>
4. sw.js を書く
const CACHE_NAME = 'app-cache-v1';
const PRECACHE = [
'/',
'/index.html',
'/offline.html',
'/manifest.json',
'/icons/icon-192.png',
'/icons/icon-512.png',
];
// インストール時に、必要なファイルをキャッシュする
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE))
);
self.skipWaiting();
});
// 有効化時に、古いキャッシュを削除する
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(
keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k))
)
)
);
self.clients.claim();
});
// リクエストのたびに呼ばれる
self.addEventListener('fetch', (event) => {
const { request } = event;
if (request.method !== 'GET') return;
// ページ遷移:ネットワーク優先、失敗したらキャッシュ → offline.html
if (request.mode === 'navigate') {
event.respondWith(
fetch(request).catch(async () => {
const cached = await caches.match(request);
return cached || caches.match('/offline.html');
})
);
return;
}
// それ以外(CSS・画像など):キャッシュ優先、なければ取得して保存
event.respondWith(
caches.match(request).then((cached) => {
if (cached) return cached;
return fetch(request).then((response) => {
const copy = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(request, copy));
return response;
});
})
);
});
キャッシュ戦略の考え方
| 対象 | 戦略 | 理由 |
|---|---|---|
| HTML(ページ遷移) | ネットワーク優先 | 常に新しい内容を見せたい |
| CSS・画像・JS | キャッシュ優先 | 速く表示でき、オフラインでも使える |
すべてをキャッシュ優先にすると、更新しても古い画面が出続けるという問題が起きます。ページの性質に合わせて使い分けるのがポイントです。
5. 動作を確認する
- HTTPS、または
localhostでサイトを開く - Chromeの開発者ツール → Application タブを開く
-
Manifest に情報が出ていること、Service Workers に
activatedと出ていることを確認する - Service Workers の Offline にチェックを入れて、再読み込みしてもページが表示されることを確認する
- Lighthouse タブで PWA の項目を確認する
ホーム画面への追加方法
- Android(Chrome):メニューから「ホーム画面に追加」または「アプリをインストール」
- iOS(Safari):共有ボタンから「ホーム画面に追加」
つまずきやすい点
-
更新が反映されない:
sw.jsを変えたらCACHE_NAMEのバージョンも上げます。古いキャッシュが残っていると、前の画面が出続けます。 -
sw.jsの置き場所:Service Worker は、置いた場所より下の範囲しか制御できません。基本はサイトのルート直下に置きます。 - 登録に失敗する:HTTPSでないと動きません。デプロイ先がHTTPSになっているか確認します。
-
パスの間違い:
PRECACHEに存在しないファイルが1つでもあると、addAll全体が失敗します。
まとめ
- PWAの最小構成は、HTTPS・manifest・Service Worker の3つです。
- HTMLはネットワーク優先、静的ファイルはキャッシュ優先が基本です。
- 仕組みが分かったあとは、Workbox や Vite のPWAプラグインを使うと、キャッシュ管理が楽になります。
参考
ホームページ制作・PWA開発を承っています。
SE Web / PWA Studio:https://majestic-crostata-aff623.netlify.app/