Microsoft Edge で Service Worker が登録できない――とくにローカルの HTML をダブルクリックして file:// で開いた時に発生するエラーは、多くのフロントエンド開発者が最初に踏む落とし穴です。本稿では「なぜ登録できないのか」「最短で直すには」「HTTPS 前提の開発へどう移行するか」を、具体的なコマンド・コード・デバッグ手順・チェックリストまで含めて網羅的に解説します。
Microsoft Edgeで Service Workerが登録できない問題の本質
Edge のコンソールに次のメッセージが出て Service Worker が登録できないことがあります。
Uncaught (in promise) TypeError: Failed to register a ServiceWorker:
The URL protocol of the current origin ('file://') is not supported.
これはバグではなく仕様です。Service Worker はセキュリティ上の理由から「安全なコンテキスト(Secure Context)」でしか動作しないため、file:// のようにオリジンが保証されないスキームでは登録そのものが禁止されています。Edge(Chromium)だけでなく、Chrome や Firefox を含むモダンブラウザ共通の挙動です。
プロトコル別:登録の可否早見表
| アクセス方法 | 例 | Service Worker 登録 | 備考 |
|---|---|---|---|
| HTTPS | https://localhost:8443 / https://example.test | ◯ | 本番は必ずこれ。ローカルも推奨。 |
| HTTP(localhost) | http://localhost:3000 / http://127.0.0.1:5173 | ◯ | 開発特例として許可済み。 |
| HTTP(LAN・外部ホスト) | http://192.168.0.10 / http://dev.myhost | × | 原則 HTTPS が必要。 |
| FILE | file:///C:/site/index.html | × | ダブルクリックでの起動は不可。 |
| DATA / BLOB の一部 | data: / blob: | × | オリジンが制御できないため不可。 |
最短で解決する手順(ローカル Web サーバーを立てる)
解決のカギは「file:// をやめて localhost の HTTP/HTTPS で配信する」ことです。すぐに試せる代表例をいくつか挙げます。
Node.js がある場合
# プロジェクトフォルダで実行
npx http-server # 既定ポート 8080
# または
npx serve -l 3000
ブラウザで http://localhost:8080(または指定したポート)にアクセスします。
Python がある場合
# Python 3 系
python -m http.server 8000
ブラウザで http://localhost:8000 にアクセスします。
VS Code の拡張を使う場合
- Live Server をインストールし、HTML を右クリックして「Open with Live Server」。
- ホットリロード付きで素早く検証できます。
PowerShell(Windows 標準のみ)
# Windows PowerShell 5.1+
# カレントディレクトリを配信(自己責任・開発用途のみ)
Add-Type -AssemblyName System.Net.HttpListener
$h = New-Object System.Net.HttpListener
$h.Prefixes.Add("http://localhost:8888/")
$h.Start()
Write-Host "Serving http://localhost:8888/ (Ctrl+C to stop)"
while ($h.IsListening) {
$ctx = $h.GetContext()
$path = Join-Path (Get-Location) ($ctx.Request.Url.LocalPath.TrimStart('/'))
if (Test-Path $path -PathType Leaf) { $bytes = [IO.File]::ReadAllBytes($path) }
else { $path = "index.html"; $bytes = [IO.File]::ReadAllBytes($path) }
$ctx.Response.OutputStream.Write($bytes,0,$bytes.Length)
$ctx.Response.OutputStream.Close()
}
簡易実装なので、通常は前述の http-server/Live Server の利用を推奨します。
HTTPS 前提のローカル開発に移行する
本番はもちろん開発でも HTTPS での検証を勧めます。以下の選択肢があります。
自己署名証明書を許可して検証
- Edge のアドレスバーで
edge://flagsを開く。 - Allow invalid certificates for resources loaded from localhost(
#allow-insecure-localhost)を「Enabled」にする。 - 開発用サーバーを
https://localhost:<port>で起動する。
この方法は localhost 限定の緩和です。LAN 内の別ホストやカスタムドメインでは有効になりません。
開発機で本物の「信頼できる」ローカル証明書を発行する
ローカルに開発者用 CA を作成し、localhost や任意の開発用ドメイン(例:dev.example.test)に署名した証明書を発行します。代表的な手順例:
# 例:開発者用 CA を作成して OS に登録
mkcert -install
# 例:localhost/127.0.0.1/::1 向けの証明書を作る
mkcert localhost 127.0.0.1 ::1
# 生成された cert/key をローカルサーバーに設定
npx http-server --ssl --cert cert.pem --key key.pem -p 8443
.NET/Node/Go など各フレームワークにも開発証明書の仕組みがあり、dotnet dev-certs https --trust のように一発で準備できる場合もあります。
最低限の登録コードと配置の原則
Service Worker は「スコープ(scope)」という配下 URL の概念があります。原則として、Service Worker ファイル(例:/sw.js)は、制御したいパス配下に置きます。サイト全体を制御したいならルート直下が安全です。
登録コード(最小例)
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const reg = await navigator.serviceWorker.register('/sw.js', { scope: '/' });
console.log('SW registered', reg.scope);
} catch (e) {
console.error('SW registration failed:', e);
}
});
}
</script>
最小の sw.js(App Shell をキャッシュ)
const CACHE_VERSION = 'v1';
const APP_SHELL = [
'/',
'/index.html',
'/styles.css',
'/app.js',
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_VERSION).then((cache) => cache.addAll(APP_SHELL))
);
self.skipWaiting(); // 速やかに新 SW をアクティブ化
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) => Promise.all(
keys.filter((k) => k !== CACHE_VERSION).map((k) => caches.delete(k))
))
);
self.clients.claim(); // 既存ページも即制御
});
self.addEventListener('fetch', (event) => {
const req = event.request;
event.respondWith(
caches.match(req).then((cached) => {
const fetchPromise = fetch(req).then((res) => {
const copy = res.clone();
caches.open(CACHE_VERSION).then((cache) => cache.put(req, copy));
return res;
}).catch(() => cached);
return cached || fetchPromise;
})
);
});
Edge でのデバッグ手順(確実版)
- Edge を開き、F12(DevTools)→ Application → Service Workers を選択。
- Update / Unregister ボタンで更新・解除を明示的に実行。
- Clear storage(左メニュー)で Unregister service workers とキャッシュ・IndexedDB を一括クリア。
- ネットワークパネルで「Disable cache(開発者ツールを開いている間)」にチェック。
- アドレスバーで ハードリロード(Windows:
Ctrl+F5)。
古い Service Worker が残っていると挙動が分かりにくくなります。テスト前に一度「Unregister → Clear storage → Hard Reload」を行うと再現性が上がります。
よくある落とし穴と対処
| 症状 | 原因 | 対処 |
|---|---|---|
file:// では登録できない | Secure Context ではない | ローカルサーバー(localhost)で配信。できれば HTTPS。 |
| 登録は成功するが fetch が効かない | スコープ外のリクエスト | /sw.js をサイトルートに置く。scope を見直す。 |
| 更新しても古いキャッシュが返る | キャッシュキーのバージョニング不足 | CACHE_VERSION を上げ、activate で古いキャッシュを削除。 |
| ネットワークエラー時に白画面 | フォールバック未実装 | オフライン用 HTML/画像を用意して返す。 |
| スコープが意図せずサブディレクトリのみ | Service Worker を /app/sw.js に配置 | サイト全体を制御するなら /sw.js に移動。 |
| 本番だけ動かない | HTTPS 未対応 / Mixed Content / CSP | HTTPS を有効化。HTTP リソースを排除。CSP を調整。 |
| ビルドツール使用時に 404 | 出力先に sw.js がコピーされない | ビルド設定で sw.js を公開ディレクトリ直下へ出力。 |
| dev サブパスで不整合 | ベースパス設定(例:Vite の base)の不一致 | ローカルと本番で同一のベースパスを維持。 |
オフラインフォールバックの実装例
「ネットワークにもキャッシュにも無い」場合に専用ページを返して UX を向上させます。
// sw.js の一部
const OFFLINE_URL = '/offline.html';
self.addEventListener('install', (e) => {
e.waitUntil(
caches.open(CACHE_VERSION).then((c) => c.addAll([OFFLINE_URL]))
);
});
self.addEventListener('fetch', (e) => {
e.respondWith((async () => {
try {
const res = await fetch(e.request);
const copy = res.clone();
const cache = await caches.open(CACHE_VERSION);
cache.put(e.request, copy);
return res;
} catch {
const cached = await caches.match(e.request);
return cached || caches.match(OFFLINE_URL);
}
})());
});
更新制御:ユーザーに「新しいバージョンがあります」を出す
Service Worker の更新は自動ですが、即時反映させるにはユーザー介入や UI が必要な場合があります。
// sw.js
self.addEventListener('install', () => self.skipWaiting()); // 即時アクティブ化の準備
self.addEventListener('activate', () => self.clients.claim());
// ページ側(main.js など)
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('controllerchange', () => {
// ここで「新しいバージョンに更新します」ダイアログを出してリロードするなど
console.log('New SW took control. Reload to apply.');
});
}
registration.update() をページロード時に明示的に呼ぶと、バックグラウンド更新の頻度を高められます。
Edge でのトラブルシューティング・コマンド
コンソールから状態を確認できます。
// すべての登録を取得
navigator.serviceWorker.getRegistrations().then(rs => console.table(rs.map(r => ({
scope: r.scope,
installing: r.installing?.state,
waiting: r.waiting?.state,
active: r.active?.state
}))));
// 手動で更新を要求
navigator.serviceWorker.getRegistration().then(r => r?.update());
// 待機中の SW を即アクティブ化(ページ起点)
navigator.serviceWorker.getRegistration().then(r => r?.waiting?.postMessage({type:'SKIP_WAITING'}));
上記の postMessage に対応するため、sw.js 側に次のハンドラーを仕込んでおくと便利です。
self.addEventListener('message', (e) => {
if (e.data&&e.data.type === 'SKIP_WAITING') { self.skipWaiting(); }
});
本番デプロイ前のチェックリスト
- 配信は HTTPS になっている。
sw.jsはサイトルートに存在し、期待するscopeで登録される。- キャッシュ名にバージョンを付け、
activateで古いキャッシュを消す。 - オフラインフォールバック(HTML/画像)を持つ。
- Mixed Content を含まない(HTTP リソースを読み込んでいない)。
- アセットの長期キャッシュ戦略(ファイル名ハッシュ or キャッシュヘッダー)が整備されている。
- DevTools の Application → Service Workers で Update/Unregister が期待通り動く。
セキュリティ背景:なぜ file:// がダメなのか
Service Worker はネットワークを横取り(プロキシ)できる強力な仕組みで、XSS や改ざんが発生すると被害が大きくなります。そのため「同一オリジン」「HTTPS による暗号化・完全性保証」といった条件を満たす必要があります。file:// はユーザーごと・ファイルごとにオリジンが安定せず、意図せぬ別ファイルへのアクセスや権限昇格の危険があるため、仕様として禁止されているのです。
開発フレームワーク別のヒント
- Vite/Next.js/CRA など:開発サーバーは既定で
http://localhostを使用します。Service Worker をルートへ出力する設定(例:public/sw.js)を確認し、base(サブパス配信)を使う場合は登録パスも合わせます。 - Workbox:プリキャッシュとランタイムキャッシュの両輪で、更新時の競合が減ります。生成物の
sw.jsがルート直下へ出るように設定します。 - ASP.NET / IIS Express:
dotnet dev-certs https --trustを実行し、プロジェクトを HTTPS で起動します。静的ファイルのキャッシュヘッダーはミドルウェアで制御できます。
「再現から修復」までの最短フロー(保存版)
- HTML を ダブルクリックで開かない。必ずローカルサーバーで配信。
- 最短テスト:
npx http-server→http://localhost:8080。 - Edge DevTools → Application → Service Workers で状態確認。
- 必要に応じて Unregister → Clear storage → Hard Reload。
- HTTPS が必要なら
#allow-insecure-localhostを有効化、または開発証明書を導入。 sw.jsをサイトルートへ、scope:'/'で登録。- バージョニングとオフラインフォールバックを実装。
トラブル事例:サブディレクトリ配信
例として、アプリを https://example.com/app/ 配下にデプロイする場合を考えます。
sw.jsは/app/sw.jsに置く(/直下に置くと/app/以外も制御するため注意)。- 登録コードは
navigator.serviceWorker.register('/app/sw.js', { scope: '/app/' })。 - ビルド時に
APP_SHELLの各パスを/app/...へ合わせる。
逆にサイト全体を制御したいなら、/sw.js と scope:'/' に統一します。スコープのミスマッチは「登録は成功するがキャッシュされない」典型要因です。
ネットワーク戦略の実践例
API は Network First、静的アセットは Stale-While-Revalidate など、用途に応じた戦略を混在させると体験が安定します。
// sw.js の一部
const RUNTIME = 'runtime-v1';
self.addEventListener('fetch', (e) => {
const url = new URL(e.request.url);
// API は Network First
if (url.pathname.startsWith('/api/')) {
e.respondWith((async () => {
try {
const res = await fetch(e.request);
const copy = res.clone();
const cache = await caches.open(RUNTIME);
cache.put(e.request, copy);
return res;
} catch {
return caches.match(e.request);
}
})());
return;
}
// CSS/JS/画像は Stale-While-Revalidate
if (/.(css|js|png|jpg|svg)$/.test(url.pathname)) {
e.respondWith((async () => {
const cache = await caches.open(RUNTIME);
const cached = await cache.match(e.request);
const network = fetch(e.request).then((res) => {
cache.put(e.request, res.clone());
return res;
}).catch(() => cached);
return cached || network;
})());
}
});
ログと監視
- 登録時・インストール時・アクティベート時に
console.logを入れておく。 - 致命エラーは
console.errorとともにself.registration.showNotificationなどで気づける仕組みを作る。 - 致命的な例外で SW が停止していないか、DevTools の Errors を確認。
まとめ
file:// では Microsoft Edge を含むモダンブラウザの仕様として Service Worker は登録できません。ローカルでも localhost の HTTP/HTTPS で配信し、可能なら HTTPS 前提で開発するのが最短で確実な解決策です。sw.js の配置と scope を整え、キャッシュのバージョニングとデバッグ手順(Unregister / Clear storage / Hard Reload)を習慣化すれば、Edge でも安定してオフライン機能とキャッシュ戦略を活用できます。
付録:一気通貫の最小プロジェクト例
以下をそのまま保存して npx http-server で配信すれば、Edge での登録〜キャッシュ〜オフラインフォールバックまで確認できます。
index.html
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Edge SW Sample</title>
<link rel="stylesheet" href="/styles.css">
<h1>Service Worker Sample</h1>
<p>このページはオフラインでも表示されます。</p>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const reg = await navigator.serviceWorker.register('/sw.js', { scope: '/' });
await reg.update(); // 更新を積極的に確認
console.log('registered:', reg.scope);
} catch (e) {
console.error(e);
}
});
}
</script>
styles.css
body { font-family: system-ui, sans-serif; margin: 2rem; }
h1 { font-size: 1.8rem; }
offline.html
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Offline</title>
<h1>オフラインです</h1>
<p>ネットワークに接続できません。再接続後にリロードしてください。</p>
sw.js
const STATIC_CACHE = 'static-v1';
const RUNTIME_CACHE = 'runtime-v1';
const STATIC_ASSETS = ['/', '/index.html', '/styles.css', '/offline.html'];
self.addEventListener('install', (e) => {
e.waitUntil(caches.open(STATIC_CACHE).then((c) => c.addAll(STATIC_ASSETS)));
self.skipWaiting();
});
self.addEventListener('activate', (e) => {
e.waitUntil(caches.keys().then((keys) => Promise.all(
keys.filter((k) => ![STATIC_CACHE, RUNTIME_CACHE].includes(k)).map((k) => caches.delete(k))
)));
self.clients.claim();
});
self.addEventListener('fetch', (e) => {
const { request } = e;
// ナビゲーションは Network First → Offline fallback
if (request.mode === 'navigate') {
e.respondWith((async () => {
try {
const res = await fetch(request);
const copy = res.clone();
const cache = await caches.open(RUNTIME_CACHE);
cache.put(request, copy);
return res;
} catch {
return (await caches.match('/offline.html')) || Response.error();
}
})());
return;
}
// 静的アセットは Stale-While-Revalidate
e.respondWith((async () => {
const cache = await caches.open(RUNTIME_CACHE);
const cached = await cache.match(request);
const network = fetch(request).then((res) => { cache.put(request, res.clone()); return res; })
.catch(() => cached);
return cached || network;
})());
});
self.addEventListener('message', (e) => {
if (e.data?.type === 'SKIP_WAITING') self.skipWaiting();
});
最後に
Edge で Service Worker が登録できない時は「file:// で開いていないか」をまず疑い、localhost 配信 → DevTools でクリーンアップ → スコープの見直し → HTTPS 対応という順序で進めると短時間で解決できます。本記事の手順とスニペットをそのまま開発テンプレートとして活用してください。

コメント