Web Worker と Service Worker の違い、各々が何をするか、いつ使うべきかを学び、ページの応答性向上、バックグラウンド処理、キャッシュ、オフライン対応にどう役立つかを理解します。

ブラウザはほとんどの JavaScript をメインスレッドで実行します—ユーザー入力、アニメーション、ページの描画と同じ場所です。そこに重い処理(大きなデータの解析、画像処理、複雑な計算)があると、UI がカクついたり「フリーズ」したように見えます。ワーカーは特定のタスクをメインスレッドから切り離す、あるいはページの直接管理外に置くことで、アプリの応答性を保つために存在します。
ページが 200ms の計算で忙しいと、ブラウザはスムーズにスクロールできず、クリックに反応せず、アニメーションを 60fps で維持できません。ワーカーはバックグラウンドで処理を行い、メインスレッドがインターフェースに集中できるようにします。
Web Worker はページから作成するバックグラウンドの JavaScript スレッドです。UI をブロックする CPU 集中型の作業に向いています。
Service Worker はウェブアプリとネットワークの間に入る特殊なワーカーです。リクエストを横取りしたり、レスポンスをキャッシュしたり、オフライン対応やプッシュ通知のような機能を可能にします。
Web Worker を別室で計算するヘルパーだと考えてください。メッセージを送ると処理し、結果を返してくれます。
Service Worker は玄関の門番のようなものです。ページ、スクリプト、API 呼び出しのリクエストがそこを通り、ネットワークから取るかキャッシュから返すか、あるいは別の応答を返すかを決められます。
この記事の終わりには以下がわかります:
postMessage のようなメッセージングがワーカーモデルにどのように組み込まれるか、オフラインに重要な Cache Storage API がなぜ重要かこの概要で「なぜ」やメンタルモデルを固めたら、次に各ワーカー種別の振る舞いと実際のプロジェクトでの位置づけを詳しく見ていきます。
ウェブページを開いたとき、ユーザーが“感じる”ほとんどは メインスレッド 上で起きます。ピクセルの描画(レンダリング)、タップやクリックへの反応(入力)、そして多くの JavaScript の実行がそこにあります。
レンダリング、入力処理、JavaScript が同じスレッドで順番に処理されるため、ひとつの重いタスクが他を待たせてしまいます。だからパフォーマンス問題は単なる「遅いコード」ではなく、応答性 の問題として現れがちです。
ブロッキングがユーザーに与える感覚:
JavaScript には fetch() やタイマー、イベントなど多くの非同期 API がありますが、非同期が重い処理をレンダリングと同時に「並列」で実行してくれるわけではありません。
画像処理、大きな JSON の計算、暗号処理、複雑なフィルタリングなどの高負荷計算をメインスレッドで行えば、UI 更新と競合し続けます。非同期は実行のタイミングを遅らせることはできますが、同じスレッドで動く限り、実行時にはジャンクを引き起こす可能性があります。
ワーカーはブラウザがページの応答性を保ちながら実用的な作業を行えるようにするためにあります。
要するに:ワーカーはメインスレッドを守り、バックグラウンドで実務的な仕事をさせるための仕組みです。
Web Worker は JavaScript をメインスレッドの外で実行する方法です。レンダリングやスクロール、クリック応答と競合する代わりに、ワーカーは独立したバックグラウンドスレッドで動くため、重い処理を行ってもページが「固まった」ように感じさせません。
ページはユーザーインタラクションに集中し、ワーカーは大きなファイルのパース、数値計算、チャート用データの準備などの CPU 集中型作業を処理します。
Web Worker は独自のグローバルスコープを持つ別スレッドで実行されます。多くの Web API(タイマー、fetch(多くのブラウザで)、crypto など)にアクセスできますが、ページから意図的に分離されています。
一般的な種類としては:
初めてワーカーを使う人が見るサンプルの多くは Dedicated Worker です。
ワーカーはページ内の関数を直接呼べません。通信はメッセージで行います:
postMessage() でワーカーにデータを送る。postMessage() で応答する。大きなバイナリデータでは、ArrayBuffer の所有権を転送してコピーを避けると、メッセージパスが高速のまま保たれます。
ワーカーは分離されているため、いくつかの重要な制約があります:
window や document は使えません。ワーカーは self(ワーカーのグローバルスコープ)で実行され、利用可能な API はメインスレッドと異なります。適切に使えば、Web Worker はアプリの動作を変えずにメインスレッドのパフォーマンスを改善する最も簡単な方法のひとつです—高負荷処理を実行する場所だけを変えます。
Web Worker は、JavaScript がメインスレッドで重い仕事をしてページが「固まる」ように感じるときに適しています。メインスレッドはユーザー操作とレンダリングも担当するため、そこでの重い処理はジャンクや遅延クリック、スクロールの固まりを引き起こします。
DOM に直接アクセスする必要がない CPU 集中型の仕事があるときに Web Worker を使います:
実用的な例:大きな JSON ペイロードを受け取り、パースで UI がカクつくなら、そのパースをワーカーへ移して結果だけ返すとよいです。
ワーカーとの通信は postMessage で行います。大きなバイナリデータには transferable objects(例:ArrayBuffer)を使い、ブラウザがメモリの所有権をワーカー側に移せるようにするとコピーを避けられ高速です。
// main thread
worker.postMessage(buffer, [buffer]); // ArrayBuffer を転送する
これはオーディオバッファや画像バイトなど、大きなデータに特に有効です。
ワーカーにはオーバーヘッドがあります:追加ファイル、メッセージパス、異なるデバッグフロー。次の場合は使わないほうがよいです:
postMessage の往復が利点を打ち消してしまう場合。タスクが目に見える停止(多くの場合 ~50ms 以上)を引き起こし得て、かつ「入力 → 計算 → 出力」と表現できて DOM アクセスを必要としないなら、Web Worker を採用する価値があります。作業が主に UI の更新であれば、メインスレッド上で最適化する方がよいです。
Service Worker はブラウザのバックグラウンドで動く特殊な JavaScript ファイルで、サイトのためのプログラマブルなネットワーク層として振る舞います。ページ自体ではなく、ウェブアプリとネットワークの間に位置して、アプリがリソース(HTML、CSS、API 呼び出し、画像)を要求したときに何をするか決められます。
Service Worker は単一のタブに紐づかない独立したライフサイクルを持ちます:
ブラウザはワーカーを停止・再起動できるため、イベント駆動スクリプトとして扱い、仕事は素早く行い、状態は永続ストレージに保存し、常に常駐しているとは仮定しない設計にしてください。
Service Worker は 同一オリジン に制限され、通常ワーカーが提供されるフォルダ(およびその下位)をスコープとしてページを制御します。また、ネットワークリクエストに影響を与え得るため HTTPS(開発時は localhost を除く)が必要です。
Service Worker は主にウェブアプリとネットワークの間に位置し、いつネットワークを使うか、いつキャッシュを使うか、バックグラウンドで少し作業をするかを決められます—しかもページをブロックしません。
もっとも一般的な役割は、アセットやレスポンスをキャッシュしてオフラインや低速接続時の体験を向上させることです。
よく見るキャッシュ戦略:
これは通常 Cache Storage API と fetch イベントハンドリングで実装します。
Service Worker はリターンビジットで体感速度を上げられます:
結果としてネットワークリクエストが減り、起動が速く、不安定な接続でもより一貫したパフォーマンスが得られます。
Service Worker は プッシュ通知 や バックグラウンド同期(ブラウザ・プラットフォーム依存)を提供できます。つまり、ページが開かれていなくてもユーザーに通知したり、失敗したリクエストを後で再送したりできます。
プログレッシブウェブアプリを作るとき、Service Worker は重要な役割を担います:
一言で言えば:Web Worker は UI を固まらせずに重い処理をさせるため、Service Worker はネットワークリクエストを制御してインストール可能なアプリのように振る舞わせるためのものです。
Web Worker:CPU 集中型タスク(大きなデータのパース、サムネイル生成、数値計算)をメインスレッド外で行わせ、UI の応答性を保つためのもの。
Service Worker:リクエストの処理やアプリのライフサイクルタスク(オフライン対応、キャッシュ戦略、バックグラウンド同期、プッシュ通知)を担当し、アプリとネットワークの間に位置する。
Web Worker は通常 ページ/タブに紐づく。ページが閉じられるとワーカーも終了することが多い(SharedWorker などの特殊例を除く)。
Service Worker は イベント駆動 で、ブラウザはイベント(fetch や push)を処理するために起動し、処理が終わると停止します。つまりページが開いていなくても動作します。
Web Worker はページが行うネットワークリクエストを横取りできません。ワーカー自身が fetch() することはできますが、サイトの他の部分のリクエストを書き換えたりキャッシュしたりはできません。
Service Worker は fetch イベントでネットワークリクエストを横取りし、ネットワークを使うかキャッシュを返すか、フォールバックを返すかを決められます。
Web Worker はアプリの HTTP キャッシュを直接管理しません。
Service Worker は Cache Storage API を使ってリクエスト/レスポンスのペアを保存・提供し、オフラインキャッシュと“即時”再読み込みの基盤を作ります。
ワーカーを動かすには"どこから"実行するか、"どう読み込むか"を考える必要があります。Web Worker はページが直接作成します。Service Worker はブラウザに登録され、あなたのサイトのネットワークリクエストの前に立ちます。
Web Worker はページが作成すると動き始めます。別の JavaScript ファイルを指し、postMessage で通信します。
// main.js (ページで実行)
const worker = new Worker('/workers/resize-worker.js', { type: 'module' });
worker.postMessage({ action: 'start', payload: { /* ... */ } });
worker.onmessage = (event) => {
console.log('From worker:', event.data);
};
ワーカーファイルは単に別のスクリプト URL としてページがフェッチできるファイルですが、メインスレッド外で実行されます。
Service Worker はユーザーが訪れるページから登録する必要があります:
// main.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
登録後、ブラウザがインストール/アクティベートのライフサイクルを処理します。sw.js は install、activate、fetch などのイベントをリッスンできます。
Service Worker はネットワークリクエストを横取りしてキャッシュを操作できます。HTTP 上で登録を許可すると、ネットワーク上の攻撃者が悪意ある sw.js を差し替えて将来の訪問を制御できてしまいます。HTTPS(開発時は http://localhost)はスクリプトとそれが影響するトラフィックを保護します。
ブラウザはワーカーを通常のページスクリプトとは異なる方法でキャッシュ・更新します。更新に備えて計画してください:
sw.js/ワーカーバンドルをデプロイする)。滑らかなロールアウト戦略を後で取り入れたい場合は、/blog/debugging-workers のようなデバッグ習慣を参照して、更新に関するエッジケースを早期にとらえるとよいでしょう。
ワーカーは通常のページ JavaScript とは異なる形で失敗します:別コンテキストで動き、独自のコンソールを持ち、ブラウザにより再起動されることがあります。堅実なデバッグ習慣があれば時間を大幅に節約できます。
DevTools を開き、ワーカー専用のターゲットを探してください。Chrome / Edge では Sources にワーカーがリストされることが多く、コンソールのコンテキストセレクタから切り替えられます。
メインスレッドと同じツールを使います:
onmessage ハンドラや長時間実行される関数をステップ実行する。メッセージが“失われる”ように見える場合は両側を点検:worker.postMessage(...) を呼んでいるか、ワーカー側に self.onmessage = ... があるか、メッセージ形状が合っているかを確認してください。
Service Worker は Application パネルでデバッグするのが最適です:
また、インストール/アクティベート/フェッチのエラーはコンソールに出ることが多く、キャッシュやオフライン動作がうまくいかない原因を説明してくれます。
キャッシュの問題が最も時間を取ります:誤ったファイルをキャッシュしたり、強すぎるキャッシュで古い HTML/JS が残ることがあります。テスト時は ハードリロード を試し、実際に何が提供されているか確認してください。
現実的なテストのために DevTools を使って:
PWA を素早く反復開発するなら、予測可能な Service Worker とビルド出力を持つベースアプリを生成してからキャッシュ戦略を調整するワークフローが役立ちます。Koder.ai のようなプラットフォームは、チャットプロンプトから React ベースのウェブアプリをプロトタイプしてソースコードをエクスポートし、ワーカー設定やキャッシュルールをより短いフィードバックループで試せる点で便利です。
ワーカーはアプリを滑らかで機能的にしますが、コードが実行される場所やアクセス範囲を変えます。セキュリティ、プライバシー、パフォーマンス面のチェックをしておくと驚きのバグや不満に繋がる事態を避けられます。
Web Worker と Service Worker はどちらも 同一オリジンポリシー によって制限されます:同一のスキーム/ホスト/ポートのリソースしか直接やり取りできません(サーバー側で明示的に CORS を許可している場合を除く)。これによりワーカーが他サイトから密かにデータを取得して混ぜるのを防ぎます。
Service Worker は追加のガードレールを持ちます:通常 HTTPS が必要です(開発時の localhost を除く)。ネットワークリクエストを横取りできるため、権限のあるコードとして扱い、依存を最小限にし、動的コードの読み込みを避け、キャッシュロジックはバージョン管理して古いキャッシュが使われ続けないようにしてください。
バックグラウンド機能は予測可能であるべきです。プッシュ通知 は強力ですが、許可プロンプトは乱用しやすいです。
明確な利点があるときだけ許可を求め(例:ユーザーが設定で通知を有効にした後)、どんな通知を受け取るかを説明してください。バックグラウンドでデータを同期/プリフェッチするなら、それが何をするかを分かりやすく示しましょう—ユーザーは予期しないネットワーク活動や通知に気づきます。
ワーカーには "無料" の性能はありません。多用すると逆効果になります:
postMessage(特に大きなオブジェクト)はボトルネックになります。バッチ処理や transferables を検討してください。すべてのブラウザがすべての機能をサポートしているわけではなく、ユーザーが権限をブロックすることもあります。機能検出を行い、段階的にフォールバックしてください:
if ('serviceWorker' in navigator) {
// service worker を登録
} else {
// オフライン機能なしで継続
}
目標は:コア機能は常に動作し、オフラインやプッシュ、重い計算といった "あれば嬉しい" 機能は利用可能なときだけ追加することです。
Web Worker と Service Worker は別々の問題を解くので、重い計算と高速かつ信頼できる読み込みの両方が必要なアプリでは相性が良いです。良いメンタルモデルは:Web Worker = 計算、Service Worker = ネットワーク+キャッシュ、メインスレッド = UI です。
ユーザーが写真を編集して(リサイズ、フィルタ、背景除去)後でオフラインでも閲覧できるギャラリーを想像してください。
この「計算してからキャッシュする」アプローチは役割を明確に分け、ワーカーは結果を作り、Service Worker がそれをどのように保存・提供するかを決めます。
フィードやフォーム、フィールドデータを扱うアプリの場合:
バックグラウンド同期がなくても、Service Worker はキャッシュされたレスポンスを返して即時性を高め、裏で更新する役割を果たします。
役割を混ぜないようにする:
postMessage 経由)。いいえ。Service Worker はバックグラウンドで動き、どのページタブにも属さないため、ページの DOM(HTML 要素)に直接アクセスできません。
この分離は意図的で、ページが開かれていない状況(例えばプッシュイベントに応答する場合)でも動作するように設計されています。Service Worker がユーザーに見せる影響を与えたい場合は、postMessage 等でページに伝え、ページ側で UI を更新させます。
いいえ。両者は独立しています。
どちらも単独で使えますし、両方を組み合わせることもできます。
現代のブラウザでは Web Worker は広くサポートされており、基準的な選択肢です。
Service Worker も主要ブラウザの現在のバージョンで広くサポートされていますが、要件やエッジケースが増えます:
localhost を除く)。互換性が重要なら、Service Worker 機能は漸進的強化として扱い、まずは確かなコア体験を構築しておき、利用可能なブラウザにだけオフライン/プッシュを追加してください。
自動ではありません。
本当の効果は、ボトルネックに対して適切なワーカーを選び、計測して改善を確認することから生まれます。
Web Worker は、入力 → 計算 → 出力 と表現でき、DOM を必要としない CPU 負荷の高い処理を行うときに使います。
適した例:大きなペイロードのパース/変換、圧縮、暗号処理、画像/オーディオ処理、複雑なフィルタリングなど。作業が主に UI 更新や頻繁な DOM 読み書きであれば、ワーカーは役に立たず(そもそも DOM にアクセスできません)、使うべきではありません。
ネットワーク制御が必要なときに Service Worker を使います:オフライン対応、キャッシュ戦略、リクエストのルーティング、そして(サポートされていれば)プッシュ通知やバックグラウンド同期など。
「UI が計算中にフリーズする」問題は Web Worker が解決します。「読み込みが遅い/オフラインが使えない」問題は Service Worker の領域です。
いいえ。Web Worker と Service Worker は独立した機能です。
どちらか一方だけでも使えますし、計算とオフライン/ネットワーク制御の両方が必要なら両方を組み合わせて使えます。
主に「スコープとライフタイム」の違いです。
fetch のようなイベントで起動され、処理後にアイドル時に停止する。ページが開いていなくてもイベントで起動できる点が特徴です。いいえ。Web Worker は window / document にアクセスできません。
UI に反映する必要がある場合は、postMessage() でメインスレッドにデータを送り、ページ側で DOM を更新してください。ワーカーは純粋な計算に集中させましょう。
いいえ。Service Worker も DOM にアクセスできません。
ユーザーに見せる内容を変えたい場合は、Clients API と postMessage() などで制御下のページとやり取りし、ページ側で UI を更新させます。
両方向とも postMessage() を使います。
worker.postMessage(data)self.postMessage(result)大きなバイナリデータには transferable(例:ArrayBuffer)を使ってコピーを避けると効率的です:
Service Worker はアプリとネットワークの間に入って、Cache Storage API を使ってリクエスト/レスポンスを保存・返却できます。
よくある戦略:
リソース種類(アプリシェル vs API データ)ごとに戦略を分けるのが一般的です。
はい。ただし役割を明確に分けてください。
一般的なパターン:
このように責任を分けると背景処理と配信が混ざらず、予測しやすくなります。
それぞれの DevTools 表面を使って調べます。
onmessage にブレークポイントを置く。プロファイルを取ってメインスレッドが応答性を保っているか確認する。キャッシュの不具合を調べるときは、実際に何がネットワークから来ているか、キャッシュから来ているかを必ず確認し、オフライン/スロットリングでの再現を試してください。
worker.postMessage(buffer, [buffer]);