Skip to content
Published on

PWA & Service Workers 2026 — Workbox 7 / vite-pwa / Capacitor / TWA / iOS Web Push / Storage Buckets 深掘りガイド

Authors

プロローグ — 「PWAは死んだ」は2026年に2回目の外れ予言となった

2019年頃、韓国や日本のカンファレンス壇上でもっとも多く聞かれた言葉は「PWAは死んだ」だった。理由はシンプルだった — iOSがWeb Pushを塞ぎ、ホーム画面追加のUXが酷く、Service Workerはデバッグ地獄で、結局React NativeとFlutterが市場を取った。

2026年5月、その予言は2回目の外れになった。

  • iOS 16.4(2023年3月) 以降、Web Pushが正式に動作する。APNを経由するが標準のPush APIである。
  • iOS 17.4(2024年2月) — AppleがEUのDMA(Digital Markets Act)施行を前に、ホーム画面ウェブアプリを一度削除し、開発者・EU委員会・メディアの一斉反発を受けて 3週間で復活させた。
  • Workbox 7(Google、2023〜2025年)がキャッシュ戦略の標準となり、もはやService Workerを手で書く理由がほとんどなくなった。
  • vite-pwa / next-pwa / Astro PWA がフレームワーク統合を提供。
  • Capacitor 6 / PWABuilder 3 / TWA が「ストアに置きたい」という最後の欲望まで解決する。
  • Storage Buckets API(Chrome 122、2024年2月)がストレージをパーティション単位で扱う。
  • View Transitions cross-document(Chrome 126、2024年6月)がMPAでもSPAのような遷移を作り出す。
  • Speculation Rules が次のページをprerenderして、クリックが0msのように感じられるようにする。

本稿はその景色を — 政治ドラマからコード例、そして韓国・日本の事例まで — 一息で整理する。


第1章 · 2026年のPWAマップ — 復活か、それともまた停滞か

まず一枚の絵。「PWA」という言葉が指しているのは実は 5つの要素の集合体 である。

[Webページ]
   |
   v
1. Manifest (web app manifest)  -- アイコン・名前・テーマ・display:standalone
   |
   v
2. Service Worker               -- バックグラウンドスレッド・キャッシュ・インターセプト
   |
   v
3. HTTPS                        -- Service Workerの前提
   |
   v
4. Installable                  -- ホーム画面追加・beforeinstallprompt
   |
   v
5. Capable APIs                 -- Push・Notification・Bluetooth・FS Access・...

5つ揃えばPWA、3つ程度なら「Installable Web」、2つならただのウェブサイトだ。

2026年の変化は、1・2・3はほぼ変わっていないのに、4と5で大事件が起きたこと である。

領域2019年の状況2026年の状況
iOS Web Push不可動作する (iOS 16.4+)
iOS ホーム画面WebApp限定的だが動作動作する。一度削除危機を経て復活
Push APIChrome / Firefox / Edgeすべての主要ブラウザ
File SystemダウンロードのみFile System Access API (Chrome/Edge)
ストレージ単一originプールStorage Bucketsでパーティション可能
ページ遷移SPAでのみ滑らかView Transitions cross-documentでMPAも可能
Prerenderrel=prefetchのみSpeculation Rules
フレームワーク統合手書きvite-pwa / next-pwa / Astro PWA
ストア配布困難または不可PWABuilder / Capacitor / TWA

復活か?半分はイエス。「App Storeか何もないか」の時代は終わった。 しかし同時にReact Native、Flutter、Capacitorが取った領域もそのままだ。2026年のPWAは「選択肢の一つ」になり、それがこれまでで最も健全な立ち位置だ。


第2章 · iOS Web Push (iOS 16.4+) — ついに到着

PWAの勢いを止めた唯一の失格要因はiOS Web Pushの不在だった。2023年3月のiOS 16.4がそれを終わらせた。

条件は明示的だ。

  1. ホーム画面に追加されたPWAでなければならない(ブラウザのタブからは不可)。
  2. ユーザーが明示的に Notification.requestPermission() を呼び出した結果が granted でなければならない。
  3. サイトはHTTPSでなければならない。
  4. マニフェストが display: standalone または display: fullscreen でなければならない。

コードは標準のPush APIそのままだ。

// 1) パーミッション要求 — 必ずユーザージェスチャ(click)内で
button.addEventListener('click', async () => {
  const permission = await Notification.requestPermission()
  if (permission !== 'granted') return

  const reg = await navigator.serviceWorker.ready
  const subscription = await reg.pushManager.subscribe({
    userVisibleOnly: true, // iOSではtrue必須
    applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
  })

  // 購読情報をサーバーへ送信
  await fetch('/api/push/subscribe', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(subscription),
  })
})

function urlBase64ToUint8Array(base64String) {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/')
  const raw = atob(base64)
  return Uint8Array.from([...raw].map((c) => c.charCodeAt(0)))
}

Service Worker側も標準そのまま。

// sw.js
self.addEventListener('push', (event) => {
  const data = event.data?.json() ?? { title: 'New', body: '' }
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/icons/icon-192.png',
      badge: '/icons/badge-72.png',
      data: { url: data.url },
    })
  )
})

self.addEventListener('notificationclick', (event) => {
  event.notification.close()
  event.waitUntil(clients.openWindow(event.notification.data.url))
})

iOSの落とし穴。

  • APNs経由。 AppleがWeb Push標準プロトコルをAPNメッセージに変換して送る。バックエンドコードは変わらないが、平均配信遅延がAndroidより2〜5秒長い。
  • userVisibleOnly: true 必須。 ユーザーに見えないsilent pushはiOSでは不可能。
  • パーミッション要求は必ずユーザージェスチャから。 ページロード直後に自動で呼ぶと無条件で拒否される。
  • 通知パーミッションUXがOS設定の奥深くに埋もれている。 一度拒否されると、ユーザーが自分で設定アプリから戻さなければならない。

体感として Android Chromeの95%の品質 には到達している。カカオ・LINE・メルカリが全社採用したのはこのためだ。


第3章 · iOS 17.4 PWA削除 → 復活 (2024.2) — EU政治ドラマ

2024年2月、AppleはiOS 17.4ベータに衝撃的な変更を入れた — EUユーザーのホーム画面ウェブアプリが、通常のブラウザのショートカットアイコンのように動作する。 フルスクリーンも、Service Workerも、Pushも、独立したストレージもなくなった。

理由はDMA(Digital Markets Act)。EUのDMAは「ゲートキーパー」のAppleに第三者ブラウザエンジンの許可を強制する。Appleの論理は 「Safari以外のエンジンがPWAをホストするなら、セキュリティとプライバシーのモデルをすべてのエンジンに対して作らなければならないが、それは私たちのロードマップにない」 だった。

業界の反応。

  • Mozilla、Vivaldi、Open Web AdvocacyなどがEU委員会に一斉に抗議文を提出。
  • 英国・EU・米国の開発者コミュニティが「これはDMA回避だ」と立ち上がった。
  • EU委員会が公式に「調査する」と発表。

3週間後、Appleは決定を覆した。 iOS 17.4正式リリース(2024年3月5日)でPWAはEUでもそのまま動作するようになった。ただし「WebKit専用」という条件付きで — 他のブラウザエンジンを使うPWAは依然として不可能。

日付出来事
2023年3月iOS 16.4 — Web Push正式対応
2024年2月初旬iOS 17.4ベータ — EU PWA削除を発表
2024年2月中旬Mozilla / OWA / EU委員会の抗議開始
2024年2月27日Apple復活を発表
2024年3月5日iOS 17.4正式版 — PWA復活(WebKit限定)
2025年iOS 18シリーズ — PWA動作安定、Web Push拡大
2026年現在iOS 18.x — PWA + Web Pushがベースライン動作

このドラマはPWA陣営に2つを教えた。

  1. 政治的に脆弱。 プラットフォーム1社の決定でグローバルユーザーの半分が消える可能性がある。
  2. それでも生き残る。 十分に多くの開発者が怒れば、決定はひっくり返る。

第4章 · Service Worker 2026 — 何が変わったのか

Service Worker仕様自体は2014年からほぼそのままだ。変わったのは 周辺エコシステムとベストプラクティス である。

核となるライフサイクルは記憶しておくと良い。

register
   |
   v
parse install   --(skipWaiting?)--> activate
   |                                    |
   |                                    v
   |                                  fetch / push / sync / message
   |                                    |
   |                                    v
   |                                  ... 新バージョンinstall
   |                                    |
   |                                    v
   |                              waiting (oldがcontrol中)
   |                                    |
   +-- update found ---------------------+

2026年のベストプラクティス。

  1. skipWaiting() は慎重に。 即時アクティブ化すると、すでに開いているタブのクライアントが旧SWと通信していて衝突する可能性がある。ユーザーに「アップデートがあります、リロードを」というトーストを見せるパターン が標準。
  2. fetch ハンドラは短く。 ハンドラ内部で重い同期コードを書くと、すべてのネットワークリクエストが遅くなる。
  3. タイムアウトは自分で設定する。 fetch() にはデフォルトタイムアウトがない。AbortController を使う。
  4. バージョン管理は明示的に。 CACHE_VERSION 定数を置き、アクティブ化時に古いキャッシュを掃除する。

最小のService Worker。

// sw.js
const CACHE_VERSION = 'v2026-05-16'
const PRECACHE = `precache-${CACHE_VERSION}`
const RUNTIME = `runtime-${CACHE_VERSION}`

const PRECACHE_URLS = ['/', '/offline.html', '/styles.css', '/app.js']

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches
      .open(PRECACHE)
      .then((cache) => cache.addAll(PRECACHE_URLS))
      .then(() => self.skipWaiting())
  )
})

self.addEventListener('activate', (event) => {
  const valid = new Set([PRECACHE, RUNTIME])
  event.waitUntil(
    caches
      .keys()
      .then((keys) => Promise.all(keys.filter((k) => !valid.has(k)).map((k) => caches.delete(k))))
      .then(() => self.clients.claim())
  )
})

self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url)
  if (event.request.method !== 'GET') return
  if (url.origin !== self.location.origin) return

  // ネットワーク優先、失敗したらキャッシュ
  event.respondWith(
    fetch(event.request)
      .then((res) => {
        const copy = res.clone()
        caches.open(RUNTIME).then((cache) => cache.put(event.request, copy))
        return res
      })
      .catch(() => caches.match(event.request).then((r) => r || caches.match('/offline.html')))
  )
})

これが長く感じられたら正常だ。2026年にはほぼ全員がWorkboxで書く。


第5章 · Workbox 7 — 標準ライブラリ

WorkboxはGoogleが作ったService Workerライブラリだ。Workbox 7(2024年1月リリース、その後7.xで安定化)がデファクト標準。

主要モジュール。

  • workbox-routing — ルーティング。registerRoute(matcher, handler)
  • workbox-strategies — キャッシュ戦略5種。CacheFirst, NetworkFirst, StaleWhileRevalidate, CacheOnly, NetworkOnly
  • workbox-precaching — ビルドマニフェストベースのプリキャッシュ。
  • workbox-expiration — TTL・LRU。
  • workbox-background-sync — Background Sync APIラッパー。
  • workbox-broadcast-update — 新しいレスポンスがあったときにメインスレッドに通知。
  • workbox-window — メインスレッド側のSW管理ヘルパー。

5行のWorkbox SW。

// sw.js
import { precacheAndRoute } from 'workbox-precaching'
import { registerRoute } from 'workbox-routing'
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'
import { ExpirationPlugin } from 'workbox-expiration'

// ビルド時に注入されるマニフェスト(vite-pwa / next-pwa が自動で埋める)
precacheAndRoute(self.__WB_MANIFEST)

// 画像 — キャッシュ優先、30日保持、最大60件
registerRoute(
  ({ request }) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images',
    plugins: [new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 30 * 24 * 60 * 60 })],
  })
)

// API — ネットワーク優先、3秒タイムアウト、失敗時はキャッシュ
registerRoute(
  ({ url }) => url.pathname.startsWith('/api/'),
  new NetworkFirst({ cacheName: 'api', networkTimeoutSeconds: 3 })
)

// ページ — Stale-While-Revalidate(古いキャッシュを即返し、バックグラウンドで更新)
registerRoute(
  ({ request }) => request.mode === 'navigate',
  new StaleWhileRevalidate({ cacheName: 'pages' })
)

この5行に1週間分の思考が詰まっている。手書きすると100行を超えるコードだ。

戦略をいつ使うかを知ることがPWA設計の半分。

戦略いつ
CacheFirst画像・フォント・ハッシュ付きJS — ほぼ不変な静的アセット
NetworkFirstAPI・ニュースフィード — 鮮度が重要
StaleWhileRevalidateHTMLページ・CSS — 即時表示 + バックグラウンド更新
CacheOnlyオフライン専用アセット
NetworkOnlyPOST・決済・ログ — 絶対にキャッシュしてはならないリクエスト

第6章 · vite-pwa / next-pwa / Astro PWA — フレームワーク統合

sw.jsを手で触る時代は終わった。2026年はビルドシステムが代わりにやってくれる。

vite-pwa

@vite-pwa/vite(2024〜2025年で最も急成長)が事実上のViteの標準。

// vite.config.ts
import { defineConfig } from 'vite'
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  plugins: [
    VitePWA({
      registerType: 'autoUpdate', // 新SW発見時に自動更新
      injectRegister: 'auto',
      workbox: {
        globPatterns: ['**/*.{js,css,html,svg,png,ico,woff2}'],
        runtimeCaching: [
          {
            urlPattern: /^https:\/\/api\.example\.com\//,
            handler: 'NetworkFirst',
            options: { cacheName: 'api', networkTimeoutSeconds: 3 },
          },
        ],
      },
      manifest: {
        name: 'My App',
        short_name: 'MyApp',
        theme_color: '#000000',
        background_color: '#ffffff',
        display: 'standalone',
        icons: [
          { src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png' },
          { src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png' },
          {
            src: '/icons/maskable-512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'maskable',
          },
        ],
      },
    }),
  ],
})

これだけ。vite build でsw.js、manifest.webmanifest、マニフェスト注入がすべて自動。

React側の登録は。

import { registerSW } from 'virtual:pwa-register'

const updateSW = registerSW({
  onNeedRefresh() {
    if (confirm('新しいバージョンがあります。リロードしますか?')) updateSW(true)
  },
  onOfflineReady() {
    console.log('オフラインで利用可能です')
  },
})

next-pwa

Next.jsはやや事情が複雑。App Router(13.4+)がRSCを導入してSWと衝突する箇所が増え、メンテナのshadowwalkerの next-pwa(2018〜)が2024年後半に手を離した結果、コミュニティが @ducanh2912/next-pwa に移行した。2026年5月現在もっとも使われているパッケージは @ducanh2912/next-pwa または @serwist/next

// next.config.js
const withPWA = require('@ducanh2912/next-pwa').default({
  dest: 'public',
  cacheOnFrontEndNav: true, // App Routerナビゲーションをキャッシュ
  aggressiveFrontEndNavCaching: true,
  reloadOnOnline: true,
  swcMinify: true,
  workboxOptions: {
    disableDevLogs: true,
  },
})

module.exports = withPWA({
  reactStrictMode: true,
})

App Routerの落とし穴 — RSCペイロード(?_rsc=)リクエストをキャッシュするとコンテンツが永久に古い状態で止まる。最新の @ducanh2912/next-pwa はこれを自動回避するが、自分でWorkboxルートを書くならRSCリクエストパターンをNetworkOnlyに振り分けるべき。

Astro PWA

@vite-pwa/astro — Astroは静的なビルド成果物なのでPWAとの相性が良い。

// astro.config.mjs
import AstroPWA from '@vite-pwa/astro'

export default {
  integrations: [
    AstroPWA({
      registerType: 'autoUpdate',
      manifest: { /* ... */ },
      workbox: { /* ... */ },
    }),
  ],
}

ブログ・ドキュメント・ランディングでもっとも上手く動く。動的ルートが少ないほどPWAが単純になるという一般法則の一例。


第7章 · Capacitor — Ionicチーム

Capacitorは「自分のWebアプリをネイティブシェルで包んでApp Storeに出したい」という欲望への答えだ。Ionicチームが作り、2026年5月時点で Capacitor 6(2024年4月メジャー、その後6.xで安定)が安定版。

# 新規Capacitorプロジェクト(既存Webアプリに被せる)
npm i @capacitor/core @capacitor/cli
npx cap init my-app com.example.myapp --web-dir=dist

npm i @capacitor/ios @capacitor/android
npx cap add ios
npx cap add android

# ビルド後同期
npm run build
npx cap sync
npx cap open ios   # Xcodeを開く
npx cap open android  # Android Studioを開く

Capacitorプラグインエコシステム。

プラグイン用途
@capacitor/cameraカメラ・ギャラリー
@capacitor/filesystemファイルシステム
@capacitor/push-notificationsAPN・FCM
@capacitor/geolocation位置情報
@capacitor/preferencesキー値ストア
@capacitor/shareOS共有シート
@capacitor/biometricFace ID・指紋
@capacitor/in-app-browser内部ブラウザ
@capacitor/local-notificationsローカル通知
@capacitor/appアプリライフサイクル

呼び出しパターンは標準JS APIと非常に似ている。

import { Camera, CameraResultType } from '@capacitor/camera'

const photo = await Camera.getPhoto({
  quality: 90,
  allowEditing: false,
  resultType: CameraResultType.Uri,
})
imgElement.src = photo.webPath

Capacitorの長所。

  • Webアプリ1セットでiOS・Android・Webすべてをカバー。
  • ネイティブAPIが必要なときだけプラグインを差し込む — それ以外はそのままWeb。
  • React Nativeと違って レンダリングはWebView なので、Web技術がそのまま使える。

短所。

  • WebViewレイヤが1段あるので、React Nativeより微妙に遅い。
  • iOSではWKWebView、AndroidではChrome Custom Tabs — 両者の差異がたまにデバッグの落とし穴になる。
  • App Store審査 — 「単なるWebViewラッパー」という理由で却下されることがある。ネイティブ機能を最低1つは使うこと。

第8章 · PWABuilder (MS) — ストアラッピング

MicrosoftのPWABuilderは PWA URL一つを入れればすべてのストア向けパッケージを作ってくれる魔法のウィザード だ。https://www.pwabuilder.com にURLを入れれば、スコアと共に各ストア向けのパッケージをダウンロードできる。

  • Microsoft Store (MSIX) — Windows
  • Google Play (Android、TWAベース).aab ファイル
  • App Store (iOS、Capacitorベース) — Xcodeプロジェクト
  • Meta Quest Store — VRアプリ
  • Samsung Galaxy Store — Androidバリエーション

CLIでも使える。

npm i -g @pwabuilder/cli

# Androidパッケージ生成(TWA)
pwabuilder package --type android --url https://example.com

# iOSパッケージ生成
pwabuilder package --type ios --url https://example.com

# Windowsパッケージ生成
pwabuilder package --type windows --url https://example.com

生成されたAndroidパッケージをそのままPlay Consoleにアップすれば完了。署名だけ別途必要。

PWABuilderの真の価値。

  • マニフェストスコア。 PWABuilderはmanifest、SW、セキュリティ、アイコン、メタデータを全部検査してスコアを出す。「100点を取る」こと自体が良いチェックリストになる。
  • 品質ゲート。 Google Playは既にPWAパッケージを受け入れているが、Microsoftが検証したパッケージという信頼が付いてくる。

第9章 · TWA (Trusted Web Activities) — Android Chrome

TWAはChrome Custom Tabsの拡張版。「自分のドメインのPWAを、フルスクリーンで、Chrome UIなしで、まるでネイティブアプリのように起動する。」

動作原理。

  1. Androidアプリ(.apk または .aab)に自分のPWAのURLを埋め込む。
  2. AndroidはそのURLをChromeでレンダリングするが、Chrome UIを隠す。
  3. Digital Asset Links でドメインがそのアプリを信頼することを証明する — これが「trusted」の意味。

assetlinks.json をドメインの /.well-known/assetlinks.json に配置する。

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.myapp",
      "sha256_cert_fingerprints": [
        "AA:BB:CC:..."
      ]
    }
  }
]

Bubblewrap CLI(Google公式)でTWAパッケージを1行で作れる。

npm i -g @bubblewrap/cli

bubblewrap init --manifest https://example.com/manifest.webmanifest
bubblewrap build
# → app-release-signed.aab 生成

TWAの長所。

  • 本物のChromeエンジンなので互換性100%。
  • Service Worker・Push・File System Accessなど全Chrome機能が使える。
  • アップデートが即時反映 — アプリ再配布なしでWebだけ更新すれば完了。

落とし穴。

  • iOSには存在しない。iOSではCapacitor・PWABuilder iOSルートを使う必要がある。
  • ユーザーがChromeを無効化するとTWAが動作しない(システムWebViewにフォールバック)。

第10章 · File System Access API — Chrome/Edge限定

Webアプリがユーザーのローカルファイルを直接読み書きするAPI。Photoshop on Web、Figma、VS Code Webがすべて使っている。

// ファイルを開く
const [handle] = await window.showOpenFilePicker({
  types: [{ description: 'Text', accept: { 'text/plain': ['.txt', '.md'] } }],
})
const file = await handle.getFile()
const text = await file.text()

// 同じファイルに書き戻す(追加パーミッション不要)
const writable = await handle.createWritable()
await writable.write(text + '\n編集済み')
await writable.close()

// ディレクトリ全体
const dirHandle = await window.showDirectoryPicker()
for await (const [name, entry] of dirHandle.entries()) {
  console.log(name, entry.kind) // 'file' または 'directory'
}

2026年5月時点のサポート状況。

  • Chrome / Edge / Opera: 正式
  • Firefox: 非対応(Mozillaがセキュリティモデルへの懸念を表明)
  • Safari: 非対応

フォールバックパターン — <input type="file"> + Blob URLで代替。FigmaやVS Code WebもFirefoxではフォールバックモードで動く。

// ポリフィルライブラリ — browser-fs-access (Google)
import { fileOpen, fileSave } from 'browser-fs-access'

const blob = await fileOpen({ extensions: ['.txt'] })
// どこでも動く。Chrome ではFile System Access、それ以外はinput/Blobフォールバック。

第11章 · 知るべき他のCapable API — Push / Background Sync / Periodic Sync / Bluetooth / USB

Push API + Notifications

第2章で既に扱った。要点 — 2026年にはiOSを含むすべての主要ブラウザで動作する。

Background Sync API

オフラインで行ったユーザー操作(フォーム送信、メッセージ送信)を、ネットワークが戻ったときに自動で再送信する。

// メインページ
const reg = await navigator.serviceWorker.ready
await reg.sync.register('send-message')

// sw.js
self.addEventListener('sync', (event) => {
  if (event.tag === 'send-message') {
    event.waitUntil(flushPendingMessages())
  }
})

サポート: Chrome / Edge / Opera / Samsung Internet。Firefox・Safari非対応。

Periodic Background Sync

周期的(最小24時間)にバックグラウンドでデータを更新する。

const status = await navigator.permissions.query({ name: 'periodic-background-sync' })
if (status.state === 'granted') {
  await reg.periodicSync.register('refresh-feed', { minInterval: 24 * 60 * 60 * 1000 })
}

サポート: ChromeとEdgeのみ。インストールされたPWAでのみ動作。

Web Bluetooth API

Bluetooth LEデバイスと直接通信。

const device = await navigator.bluetooth.requestDevice({
  filters: [{ services: ['heart_rate'] }],
})
const server = await device.gatt.connect()
const service = await server.getPrimaryService('heart_rate')
const characteristic = await service.getCharacteristic('heart_rate_measurement')
characteristic.addEventListener('characteristicvaluechanged', (event) => {
  console.log('HR:', event.target.value.getUint8(1))
})
await characteristic.startNotifications()

サポート: Chrome / Edge / Opera。Firefox・Safari非対応。

WebUSB API

USBデバイスへの直接アクセス。Arduinoフラッシング、決済端末、産業機器制御など。

const device = await navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
await device.open()
await device.selectConfiguration(1)
await device.claimInterface(0)
const result = await device.transferIn(1, 64)

サポート: Chrome / Edge / Opera。Firefox・Safari非対応。

Web Share API

OS共有シートを呼び出す。2026年にはすべての主要ブラウザで動作(iOS Safari含む)。

await navigator.share({
  title: 'My PWA',
  text: 'これ見て',
  url: 'https://example.com',
})

第12章 · Storage Buckets API (Chrome 122、2024.2) — パーティションストレージ

既存のWebストレージ(IndexedDB、Cache、localStorage)はすべて 単一originプール に紐付けられていた。ユーザーが「Cookie とサイトデータを削除」を押すと一緒に消え、quotaも一塊で管理された。

Storage Bucketsはそれを バケット単位に切り分ける。

// バケット生成
const userDataBucket = await navigator.storageBuckets.open('user-data', {
  durability: 'strict', // 絶対に失わない
  persisted: true, // 永続パーミッション
  quota: 100 * 1024 * 1024, // 100MB
})

// Cache・IDB・SWを各バケットで別々に
const cache = await userDataBucket.caches.open('user-files')
const idb = await userDataBucket.indexedDB.open('users', 1)
const sw = await userDataBucket.getDirectory() // OPFS

// 一時バケット — ユーザーがパスワードを忘れたとき一緒に消す
const tempBucket = await navigator.storageBuckets.open('temp-uploads', {
  durability: 'relaxed',
  persisted: false,
})

// バケット削除 — origin全体ではなく、このバケットだけ
await navigator.storageBuckets.delete('temp-uploads')

なぜ重要か。

  • 選択的削除 — ゲームのセーブファイルは残してキャッシュだけ消せる。
  • 選択的永続化 — 決済レシートだけ persisted: true に設定。
  • バケット別quota — 1ドメイン内のマルチテナント(例: メール・カレンダー・ノートが1ドメインにある場合)に便利。

サポート: Chrome / Edge 122+(2024.2)。Firefox・Safariは作業中。


第13章 · View Transitions cross-document (Chrome 126、2024.6) — SPAのようなMPA

ページ間遷移を滑らかにするAPI。最初はSPA(同一文書内ルーティング)専用だったが、Chrome 126(2024.6)からcross-document(異なるページ間)もサポート された。

有効化は1行。

@view-transition {
  navigation: auto;
}

これだけ。同一origin内のページ遷移でブラウザが自動的にフェードを入れてくれる。

応用 — 2つのページに同じ view-transition-name を付けるとその要素がページをまたいでモーフする。

/* product-list.html */
.product-card img {
  view-transition-name: product-image;
}

/* product-detail.html */
.product-detail img {
  view-transition-name: product-image;
}

リストのサムネイルをクリックすると、その画像が詳細ページの大きな画像へと滑らかに拡大される。Instagramやメルカリアプリで見たあの動きそのものだ。

なぜこれがPWAの復活なのか? — 「MPA(サーバーレンダー)もSPAと同じくらい滑らかに動かせる」と証明されれば、SPAが取っていたUX優位の半分が崩れる。 PWAは本質的にMPAにも馴染むモデルだ。

サポート: Chrome 126+、Edge、Opera。Safari・Firefoxは作業中。


第14章 · Speculation Rules — 未来のページをprerender

<link rel="prefetch"> の強化版。次にユーザーが行きそうなページを HTML・JS・CSSすべて受け取ってバックグラウンドタブでプリレンダリング する。

<script type="speculationrules">
{
  "prerender": [
    { "where": { "href_matches": "/product/*" }, "eagerness": "moderate" }
  ],
  "prefetch": [
    { "where": { "href_matches": "/category/*" } }
  ]
}
</script>

eagerness オプション。

  • immediate — 発見次第prerender。
  • eager — ユーザーがリンクをホバーしたら。
  • moderate — ホバーが200msを超えたら。
  • conservative — マウスダウン直後。

prerenderされたページに移動すると 即時表示される(LCP 0ms)。 カカオ・LINE・メルカリのカタログページで次のカードクリックが「0ms」に感じられる秘密。

落とし穴。

  • prerenderされたページはバックグラウンドでSW・JSが実際に走る。アナリティクスがfalse hitを送る可能性がある。 document.prerendering をチェックしてprerender中は報告を遅らせるのが標準。
  • モバイルは同時prerenderを1個だけ許可。欲張らないこと。

サポート: Chrome / Edge。Safari・Firefoxは非対応だが、ポリフィルなしで無視されるので安全に出せる。


第15章 · 韓国 / 日本のPWA事例 — カカオ、ネイバー、メルカリ、ピクシブ

韓国

  • カカオモバイルWeb — カカオトークチャンネルページ、Kakao Payの一部フローがPWAパターン(インストール可能manifest、SWキャッシュ、Push)を積極活用。特にm.kakao.comドメインの一部はstandaloneモードで追加可能。
  • Naver Webtoon — モバイルWeb版(comic.naver.com)がSWで画像キャッシュ。マンガ1話を初めて開くと次のページを先読みしてくれる。
  • Coupang — モバイルWebがService Workerで画像・JSキャッシュ。App Bannerは自社アプリ誘導に集中。
  • Daangn Market — Web存在感は限定的だが、モバイルWebの商品詳細ページがSWキャッシュを活用。

韓国でPWA採用が比較的弱い理由は明確だ。カカオトーク・ネイバーアプリといったスーパーアプリのWebView内で動くため、ユーザー視点で「別アプリ」の境界が曖昧になる。カカオのインアプリブラウザにPWAをインストールすることはできない。

日本

  • メルカリ(Mercari) — モバイルWebがPWAとして運営されている。jp.mercari.comでmanifest、SW、Push全て動作。日本はApp Store信頼度が高くネイティブアプリが圧倒的だが、メルカリは Webだけのユーザー(軽い購入者)の比率が意外と多い。
  • Pixiv Sketch — ライブドローイングプラットフォーム。モバイルブラウザでPWAとして動作。絵を描いている最中、オフラインでも途切れず保存される。
  • Yahoo! JAPAN — 一部ページ(特にニュース)がアグレッシブなSWキャッシング。ページ遷移が非常に速い秘密。
  • Pixiv(イラスト本館) — pixiv.net自体はフルPWAではないが、Web Push使用(いいね・コメント通知)。

日本でPWAが意外と通用する理由は iOSシェアが60%を超える市場でiOS Web Pushが解禁されたインパクトが大きい ことだ。LINEは自社メッセンジャーインフラがあるので特にWeb Pushを必要としないが、小さなサービスにとってはプッシュチャネルを持てるようになっただけでも大きかった。


第16章 · 誰がPWAを選ぶべきか — 判定マトリクス

カテゴリPWA適合度理由
メディア・ブログ非常に良いSWキャッシュ・オフライン読み・Web Push
Eコマース良いカタログprerender、View Transitions、軽いインストール
SaaSダッシュボード非常に良いデスクトップPWAインストール、ショートカット、フルスクリーン
チャット・メッセンジャーふつうPushは良いがネイティブ通知・VoIPに限界
カジュアルゲーム良いCanvas・WebGL・WebGPU・Storage Buckets
3A・リアルタイムゲーム悪いグラフィックス上限・メモリ上限・App Storeマーケティング
ツール(オーディオ・動画)非常に良いFile System Access、Web MIDI、Audio API
決済・フィンテックふつうセキュリティモデル・OS認証・生体 — 一部は可能、コアはネイティブ
カンファレンス・イベントアプリ非常に良い短期利用・インストール摩擦回避
写真・お絵かきツール良いFile System Access、Canvas、OPFS

チェックリスト — PWAを選ぶ前の5問。

  1. ユーザーは毎日使うか、たまに使うか? 毎日ならネイティブが普通良い。たまにならPWA。
  2. プッシュ通知は必須か? iOSもサポート済み。ただし「正確な配信タイミング保証」が必要ならネイティブ。
  3. カメラ・Bluetooth・USBといったデバイスAPIが必要か? Chrome/Androidなら PWA で十分。iOSなら Capacitor。
  4. アプリストア露出がマーケティングの中核か? ならPWABuilder + TWA、またはCapacitor。
  5. オフラインが本当に必要か、「あったら良い」程度か? 「本当に必要」はPWAがほぼ無条件で正解。

体感の1行 — モバイル優先 + メディア/ツール + たまに使う = PWA。毎日 + デバイス深く使う = ネイティブまたはCapacitor。


エピローグ — 二度目の復活

2026年5月のPWAは、2019年のPWAと同じ言葉だが、同じ技術スタックではない。

  • 仕様はほぼそのまま — Service Worker、Manifest、Cache API。
  • ツールは完全に違う — 手書きSWからWorkbox 7へ、フレームワーク統合へ、ビルドマニフェストへ。
  • パーミッションは爆発した — iOS Web Push、File System Access、Bluetooth、USB、Storage Buckets。
  • UXはSPAを真似しない — View Transitions cross-documentとSpeculation RulesでMPAがSPAを真似できるようになった。
  • 配布経路も多様化した — PWABuilder、TWA、Capacitor。

最大の変化は 「PWAかネイティブか」の二分法が終わったこと だ。2026年の答えは 「自分のユーザーが望むもっとも軽い経路」 だ。あるユーザーにとってはそれがPWAであり、別のユーザーにとってはCapacitorシェルであり、また別のユーザーにとっては真のネイティブだ。そしてその3つすべてが同じWebコードベースから分岐できる。

iOSの政治ドラマが一度示したように、プラットフォームは気まぐれだ。しかしWeb標準は止まらない。2027年の次のラウンドは — Storage Foundation、Bluetooth on iOS、RTC over Web Push — また別の復活となるだろう。

「PWAは死ななかった。私たちが追悼式をあまりに早く開いただけだ。」

— PWA & Service Workers 2026、終わり。


参考 / References