アーキテクチャ

PWA を信頼性が高く、インストール可能で、機能豊富なものにする技術を最大限に活用するようにアプリケーションを設計するには、まずアプリケーションとその制約を理解し、両方に適したアーキテクチャを選択することから始めます。

現在、ウェブ開発には、シングルページ アプリ(SPA)とマルチページ アプリ(MPA)の 2 つの主要なアーキテクチャ パターンがあります。

シングルページ アプリは、アプリによって取得またはアプリに渡されるデータに基づいてページの HTML レンダリングのほとんどまたはすべてを制御する、クライアントサイドの JavaScript によって定義されます。アプリはブラウザの組み込みナビゲーションをオーバーライドし、ルーティング機能とビュー処理機能に置き換えます。

マルチページ アプリは通常、プリレンダリングされた HTML をブラウザに直接送信します。多くの場合、ブラウザが HTML の読み込みを完了した後にクライアントサイドの JavaScript で拡張され、ブラウザに組み込まれたナビゲーション メカニズムを使用して後続のビューを表示します。

どちらのアーキテクチャでも PWA を作成できます。

それぞれにメリットとデメリットがあるため、ユースケースとコンテキストに適したものを選択することが、ユーザーに高速で信頼性の高いエクスペリエンスを提供する鍵となります。

シングルページ アプリ

長所
  • ほとんどがページ内のアトミック更新です。
  • 起動時に読み込まれるクライアントサイドの依存関係。
  • キャッシュが使用されるため、その後の読み込みは高速です。
短所
  • 初期読み込みのコストが高い。
  • パフォーマンスは、デバイスのハードウェアとネットワーク接続によって異なります。
  • アプリの複雑さが増す。

シングルページ アプリは、次の場合に適したアーキテクチャです。

  • ユーザー操作は主に、同じページに表示される相互接続されたデータのアトミック アップデートを中心に行われます(リアルタイム データ ダッシュボードや動画編集アプリなど)。
  • アプリケーションに、クライアントサイドのみの初期化依存関係がある(たとえば、起動コストが非常に高いサードパーティ認証プロバイダ)。
  • ビューの読み込みに必要なデータは、接続されたハードウェアのコントロールの表示など、特定のクライアントサイド専用のコンテキストに依存します。
  • アプリは小さくシンプルであるため、サイズや複雑さが上記のデメリットに影響することはありません。

次のような場合は、SPA は適したアーキテクチャではない可能性があります。

  • 初期読み込みのパフォーマンスは重要です。通常、SPA は、読み込む内容とその表示方法を決定するために、さらに多くの JavaScript を読み込む必要があります。この JavaScript の解析と実行時間は、コンテンツの取得と合わせて、レンダリングされた HTML を送信するよりも遅くなります。
  • アプリは主に低電力から中電力のデバイスで実行されます。SPA はレンダリングに JavaScript に依存するため、ユーザー エクスペリエンスは MPA の場合よりも、特定のデバイスの処理能力に大きく依存します。

SPA では、ブラウザの組み込みナビゲーションを独自のルーティングに置き換える必要があるため、現在のビューを効率的に更新する、ナビゲーションの変更を管理する、ブラウザが処理するはずの以前のビューをクリーンアップするといった処理に最低限の複雑さが必要になります。そのため、全体的なメンテナンスが難しくなり、ユーザーのデバイスに負荷がかかります。

マルチページ アプリ

長所
  • 主にページ全体を更新します。
  • 初期レンダリング速度が重要です。
  • クライアントサイド スクリプトによって機能を拡張できます。
短所
  • セカンダリ ビューには別のサーバー呼び出しが必要です。
  • コンテキストはビュー間で引き継がれません。
  • サーバーまたはプリレンダリングが必要です。

マルチページ アプリは、次のような場合にアーキテクチャの選択肢に適しています。

  • ユーザー インタラクションは、ニュースアプリや e コマース アプリなど、コンテキスト ベースのデータがオプションで含まれる単一のデータのビューを中心に行われます。
  • 初期レンダリング速度は重要です。すでにレンダリングされた HTML をブラウザに送信する方が、JavaScript ベースの代替手段を読み込み、解析、実行した後にデータ リクエストから HTML を組み立てるよりも高速です。
  • クライアントサイドのインタラクティビティまたはコンテキストを、初期読み込みの後に拡張機能として含めることができます。たとえば、レンダリングされたページにプロファイルを重ねたり、クライアントサイドのコンテキストに依存するセカンダリ コンポーネントを追加したりできます。

次のような場合は、MPA が適したアーキテクチャではない可能性があります。

  • JavaScript や CSS を再ダウンロード、再解析、再実行するのは、コストがかかりすぎるため現実的ではありません。このデメリットは、Service Worker を使用する PWA では軽減されます。
  • ユーザーの位置情報などのクライアント側のコンテキストは、ビュー間でシームレスに引き継がれないため、そのコンテキストの再取得にはコストがかかる可能性があります。キャプチャして取得するか、ビュー間で再リクエストする必要があります。

個々のビューは、アクセス前にサーバーによって動的にレンダリングされるか、事前にレンダリングされる必要があるため、ホスティングが制限されたり、データの複雑さが増したりする可能性があります。

どちらを選択すればよいですか?

このような長所と短所があるにもかかわらず、どちらのアーキテクチャも PWA の作成に有効です。ニーズに応じて、アプリのさまざまな部分でこれらを混在させることもできます。たとえば、ストアの掲載情報は MPA アーキテクチャに従って、購入手続きフローは SPA アーキテクチャに従っています。

どちらを選択した場合でも、次のステップは、サービス ワーカーを最適に使用して最適なエクスペリエンスを提供する方法を理解することです。

Service Worker のパワー

Service Worker は、キャッシュされたレスポンスやネットワーク レスポンスの基本的なルーティングや配信にとどまらず、多くの機能を備えています。複雑なアルゴリズムを作成して、ユーザー エクスペリエンスとパフォーマンスを改善できます。

Service Worker に含まれる(SWI)

サイトのアーキテクチャの不可欠な部分として Service Worker を使用する新しいパターンが、Service Worker Includes(SWI)です。SWI は、個々のアセット(通常は HTML ページ)をキャッシュのニーズに基づいて分割し、サービス ワーカーでそれらをつなぎ合わせることで、キャッシュサイズを削減しながら一貫性、パフォーマンス、信頼性を向上させます。 グローバル ヘッダー、コンテンツ領域、サイドバー、フッターがあるウェブサイト。

この画像はサンプルのウェブページです。ページは 5 つのセクションに分かれています。

  • 全体的なレイアウト。
  • グローバル ヘッダー(上部の暗いバー)。
  • コンテンツ領域(中央左の行と画像)。
  • サイドバー(右中央にある高いやや濃いバー)。
  • フッター(下部のバーが暗い)

全体的なレイアウト

全体的なレイアウトは頻繁に変更される可能性は低く、依存関係もありません。これはプレキャッシュに適しています。

グローバル ヘッダーとグローバル フッターには、トップメニューやサイトのフッターなどが含まれます。これらの要素は、ページ全体がキャッシュに保存される場合、ページがキャッシュに保存されたタイミングによっては、ページの読み込みごとに変更される可能性があります。

これらを分けてコンテンツとは別にキャッシュすることで、キャッシュされるタイミングに関係なく、常に同じバージョンをユーザーに表示できます。更新頻度が低いため、プレキャッシュにも適しています。ただし、サイトの CSS と JavaScript に依存します。

CSS と JavaScript

理想的には、サイトの CSS と JavaScript は、プリキャッシュされたアセットの場合と同様に、サービス ワーカーを更新しなくても増分更新できるように、再検証中に古い状態になるようにキャッシュに保存する必要があります。ただし、サービス ワーカーが新しいグローバル ヘッダーまたはフッターで更新されるたびに、最小バージョンに維持する必要があります。このため、Service Worker のインストール時にキャッシュも最新バージョンのアセットで更新する必要があります。

コンテンツ領域

次に、コンテンツ領域があります。更新の頻度に応じて、ネットワーク ファースト、または再検証中に古いネットワークのいずれかが有効な戦略です。画像は、以前に説明したように「キャッシュ ファースト」戦略でキャッシュする必要があります。

最後に、サイドバー コンテンツにタグや関連アイテムなどのセカンダリ コンテンツが含まれていると仮定すると、ネットワークから取得するほど重要ではありません。再検証中の古いデータ戦略がこれに該当します。

ここまで読んで、このようなセクションごとのキャッシュ保存はシングルページ アプリでしかできないと思われたかもしれません。ただし、エッジサイド インクルードサーバーサイド インクルードにヒントを得たパターンを Service Worker に取り入れ、高度な Service Worker 機能を使用することで、どちらのアーキテクチャでもこれを実現できます。

実際に試す

次の Codelab で、サービス ワーカーを試すことができます。

ストリーミング レスポンス

前のページは、SPA の世界でアプリシェル モデルを使用して作成できます。このモデルでは、アプリシェルがキャッシュに保存され、サービングされ、コンテンツがクライアント側で読み込まれます。Streams API を導入して幅広く利用できるようになったことで、App Shell とコンテンツの両方を Service Worker で結合してブラウザにストリーミングできるようになり、App Shell のキャッシュの柔軟性と MPA のスピードが実現します。

その理由は次のとおりです。

  • ストリームは非同期で構築できるため、ストリームのさまざまな部分を他のソースから取得できます。
  • ストリームのリクエスト元は、アイテム全体が完了するのを待たずに、最初のデータ チャンクが利用可能になり次第、レスポンスを処理できます。
  • ストリーミング用に最適化されたパーサー(ブラウザを含む)は、ストリームが完了する前に段階的にコンテンツを表示するため、認識されるレスポンスのパフォーマンスを高速化できます。

ストリームのこれらの 3 つの特性により、ストリーミングを中心に構築されたアーキテクチャは、そうでないアーキテクチャよりもパフォーマンスが速く感じられます。

Streams API は複雑で低レベルであるため、扱いには注意が必要です。幸い、サービス ワーカーのストリーミング レスポンスを設定するのに役立つ Workbox モジュールがあります。

ドメイン、オリジン、PWA スコープ

Service Worker、ストレージ、インストールされた PWA のウィンドウなど、Web Worker はすべて、ウェブ上で最も重要なセキュリティ メカニズムの 1 つである同オリジン ポリシーによって管理されます。同じオリジン内では、権限が付与され、データが共有され、サービス ワーカーが異なるクライアントと通信できます。同じオリジン外では、権限は自動的に付与されず、データは分離され、異なるオリジン間でアクセスできなくなります。

同一オリジン ポリシー

2 つの URL は、プロトコル、ポート、ホストが同じであれば、オリジンが完全に一致していると定義されます。

たとえば、https://squoosh.apphttps://squoosh.app/v2 は同じオリジンですが、http://squoosh.apphttps://squoosh.comhttps://app.squoosh.apphttps://squoosh.app:8080 は異なるオリジンにあります。詳細と例については、同一オリジン ポリシーの MDN リファレンスをご覧ください。

ホストを変更する方法は、サブドメインの変更だけではありません。各ホストは、トップレベル ドメイン(TLD)、セカンダリ レベル ドメイン(SLD)、0 個以上のラベル(サブドメインと呼ばれることもあります)で構成され、URL 内のドットで区切られ、右から左に読み取られます。いずれかの項目を変更すると、別のホストになります。

ウィンドウ管理モジュールでは、ユーザーがインストール済みの PWA とは異なるオリジンに移動したときに、アプリ内ブラウザがどのように表示されるかを確認しました。

このアプリ内ブラウザは、ウェブサイトの TLD と SLD が同じでもラベルが異なっていても、異なるオリジンとみなされるため表示されます。

ウェブブラウジング コンテキストにおけるオリジンの重要な要素の一つは、ストレージと権限の仕組みです。1 つのオリジンは、次のような多くの機能をその中のすべてのコンテンツと PWA と共有します。

  • ストレージの割り当てとデータ(IndexedDB、Cookie、ウェブ ストレージ、キャッシュ ストレージ)。
  • Service Worker の登録。
  • 付与または拒否された権限(ウェブプッシュ、位置情報、センサーなど)。
  • ウェブプッシュ登録。

あるオリジンから別のオリジンに移行すると、以前のアクセス権はすべて取り消されるため、権限を再度付与する必要があり、PWA はストレージに保存されているすべてのデータにアクセスできなくなります。

リソース