PRPL パターンから非 SPA のパフォーマンスを考えたときのメモ
Tags: webcomponents, javascript, webdev, japanese
Published: 2017-11-27 09:27:38

---
title: PRPL パターンから非 SPA のパフォーマンスを考えたときのメモ
published: true
description: SPA ではない構成のウェブアプリでパフォーマンスを出すための施策を、PRPL パターンを発想の起点にして考えてみた。
tags: Web Components, JavaScript, WebDev, Japanese
---

SPA ではない構成のウェブアプリでパフォーマンスを出すための施策を、[PRPL](https://developers.google.com/web/fundamentals/performance/prpl-pattern/) パターンを発想の起点にして考えてみた。

# 動機

JavaScript によるコンポーネントベースの開発はフロントエンドの実装を効率的にした。

しかし toC のサービスにおいて、Bot フレンドリーであるための施策が重荷になっている。`<meta>` タグだけサーバー側で作っておく対策もあるが、Pocket や未知のサービスも含めると *本文* が必要なケースは依然として多い。個人的には、SPA をいくつかリリースしていく中で、ドキュメントとしての HTML なのにスクリプトを実行しないとドキュメントにならないことへの違和感も感じていた。

そこでドキュメントとして成立するための最小限の要素だけをサーバー側で生成することで、Bot 施策の回避を考えた。

SPA を保ったままで *ドキュメントとして成立した HTML* を扱うためには密結合な構成か SSR の要件が出てくるという判断に至り、SPA を廃止することにした。

SPA ではなくても、ドキュメント向けの要素以外は Web Components として引き続き開発できる。

その上でパフォーマンスを向上させるための施策を検討した。

# 施策

PRPL パターンを起点に考えた施策は次のとおり。

## Push

HTTP/2 の Push はブラウザのキャッシュを考慮することが現状難しいため対応を保留する。

### Endpoint

例として `/page/1` へのリクエストで、次のような HTML を返却する。

```
<!DOCTYPE html>
<html lang="en-US" dir="ltr">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1,user-scalable=yes">
    <title>Page 1</title>

    <!-- Preload CSS -->
    <link rel="preload" as="style" href="/style.css" onload="this.rel='stylesheet'">

    <!-- Preload critical Custom Element -->
    <link rel="preload" as="script" href="/x-header.js" onload="var s = document.createElement('script');
    s.src = this.href;
    document.body.appendChild(s);">

    <!-- Preload service worker -->
    <link rel="preload" as="script" href="/service-worker.js">

    <!-- Preload lazy load script -->
    <link rel="preload" as="script" href="/lazy-load.js" onload="var s = document.createElement('script');
    s.src = this.href;
    document.body.appendChild(s);">

    <!-- Prefetch next page -->
    <link rel="prefetch" href="/page/2">

    <!-- Prefetch home page -->
    <link rel="prefetch" href="/">

    <link rel="manifest" href="/manifest.json">
</head>
<body>
    <!-- Custom Element for header -->
    <x-header></x-header>

    <main>
        <article>
            <section>
                <h1>Title</h1>
                <p>The content</p>
                <p>

                    <!-- Custom Element for article -->
                    <x-content-gallery lazy-load="/x-content-gallery.js"></x-content-gallery>

                </p>
                <p><a href="/page/2">Next</a></p>
            </section>
        <article>
    </main>

    <!-- Custom Element for footer -->
    <x-footer lazy-load="/x-footer.js"></x-footer>

    <script async defer src="/polyfill-of-preload.js"></script>
    <script>
        // Add a service worker
        'serviceWorker'in navigator&&window.addEventListener('load',function(){navigator.serviceWorker.register('/service-worker.js')});
    </script>
</body>
</html>
```

クリティカルなリソースの `preload` と、遷移する可能性のあるページの `prefetch` を行なう。

`lazy-load` 属性が追加されている要素は、その要素がビューポート内に出現した時点で依存解決をする。この属性名は仮なのと、ビューポートに出現した時点では UX を損なうリスクもあるため工夫は必要。

## Render

HTML をレンダリングする。

SPA ではないので異なるルートのための処理は必要ない。

## Pre-cache

SPA ではないことから、Service Worker による事前キャッシュ( Pre-cache )は行わない。

Service Worker は通信発生時のキャッシュや通知などの用途に限定される。

## Lazy-load

SPA ではないことから、PRPL パターンにおける Lazy-load とは少し趣が違う。

PRPL パターンでは異なるルートのためのリソースを遅延読込することと、画像などのリソースがビューポートに現れるときに遅延読込するというアプローチがとられると理解している。

今回は SPA ではないため、異なるルートのための遅延読込は考慮しない。

# アーキテクチャ

データソースは REST API で取得できるようにしておく。

その上でローカルとサーバーをなるべく近い構成にすることを考えると、HTML の配信は Node.js などで立てたサーバーをそのままクラウドでも走らせたい。

この場合、REST API はサーバーレスに構築できるが、HTML はサーバーレスでは構築しづらくなる。

しかしデータソースが REST API なら HTML はステートレスに返却可能なので、CDN によるキャッシュが有効に機能するはず。

---

早い段階でこの構成を実戦投入してみたい。