【視聴メモ】Rendering on the Web: Performance Implications of Application Architecture (Google I/O ’19)

この記事は Google I/O '19 のセッションの視聴メモです。
想定読者は自分なので正確性や網羅率には問題があるかもしれません。

---

## References

https://www.youtube.com/watch?v=k-A2VfuUROg

Speaker(s): Jason Miller, Houssein Djirdeh

## Abstract

> One of the core values of the web is its accessibility: the same medium can be used to deliver content that adapts to different devices, capabilities and connection types. This variability needs to be factored into every architectural decision that is made. Luckily, frameworks are here to help. This talk will showcase upcoming framework features that reduce the impact of architectural constraints and potentially improve performance beyond what is possible today.

訳

> Webの価値の一つは、ある媒体が様々なデバイス・能力・接続状況に応じて適切なコンテンツを配信できるという、一種のアクセシビリティにあります。このバラエティの豊富さはアーキテクチャがどう構成されるかに強く依存しています。幸運なことに、種々のフレームワークがそれをサポートするものとして使われています。このセッションではアーキテクチャの制約を緩め、今日可能であることを越えてパフォーマンスを潜在的に強化するような、最新のフレームワークの機能をご紹介します。

## Contents

* HTTP Archive によればモバイルページに送信されるJSのサイズは2015年の200KBから2019年は400KBまで増加した
* FCPを速くしつつ立地なアプリケーションを提供しなければいけないというのが今日のWebエンジニアの課題
* SSRはFCPを早くする一方でCSRはリッチでインタラクティブなアプリを提供する
* 両方必要なので **SSR with Hydration** という考えが一般的になった
* SSRが重要なのは3つの理由からなる
    * パフォーマンス
    * SEO
    * データの取得

**パフォーマンスについて**

* SSR with Hydration ではHTMLはいち早くクライアントに送信し、JSは必要に応じて送信する
    * 高速なFCPとインタラクティビティの両立
* ReactにおけるSSR
    * `ReactDOMServer.renderToString(<App />)` でHTMLが文字列として取得できる
    * 更に簡単にするためのフレームワークとしてNext.jsがある
* VueとAngularについても同様の説明
* SSR with hydration まとめ
    * First Paint: ↓
    * Time to First Byte: ↑
    * Time to Interactive: -
    * Frist Input Delay: ↑
* ビルドタイムPre-renderingでFCPを改善しつつSSRのオーバーヘッドと特異なインフラをなくす
* Gatsby とかがbuild-time pre-renderingをサポートしている
    * React の `renderToStaticMarkup` をビルドタイムに使う
    * メインのJSはpreloadされる
    * 遷移先はprefetchされる
* **Pre-rendering** の特徴
    * staticなコンテンツしかレンダリングできない
    * レンダリングしておくべきURLの情報が必要
    * クリティカルな問題を治すときに当該のHTMLだけ差し替えることが簡単にできる
* **Streaming SSR**
    * SSR with Hydration と Pre-rendering の中間地点
    * 一度に複数のリクエストをレンダリングする （→ HTMLの部分部分を個別にレンダリングするということ？）
    * レンダリングが完了したものから順次配信する
    * ブラウザはページのすべての要素を取得する前にレンダリングを開始できる
    * ReactはすでにStreaming SSRをサポートしている(以下）
    * Vueもサポートしている（コードは省略）

```jsx:
http.createServer((request, response) => {
  const html = ReactDOMServer.renderToNodeStream(<App />);
  html.pipe(response);
}).listen(1337);
```

* spectrum というサービスは Streaming SSR を使っている
    * 初期HTMLをチャンクにして返す
    * TTFBの改善
    * サーバーが複数ページを一度にレンダリングできるようになった
* もちろん銀の弾丸ではないよ
    * もちろんシンプルなサイトでは使えない
        * 16KB以上のHTMLがあると有効だと言われているよ
* **Progressive Hydration**
    * クライアントサイドの負荷を減らしSSRのパフォーマンスを上げる
    * 見た目上の意味がある範囲でコードを分割する（sectionで分けるとかそんな感じ）
    * 普通のSSR with Hydrationに対して、SSR with partial hydration とも呼ばれる
    * コンポーネントのJSがダウンロードされるごとにそのコンポーネントだけがインタラクティブになっていく
    * ユーザーの行動に応じてダウンロードされるJSを制御するのでリソースの絶対量を減らすことにもなる
* React + Progressive Hydration
    * Suspenseのロードマップ上に乗ってるよ
    * 現状でも自分で頑張ってコンポーネント書けば実現できるよ

```jsx:
class Hydrator extends Component {
  // re-rendering の禁止
  // Hydrator の境界をまたいでUpdateがキャスケードされるのを防ぐ
  shouldComponentUpdate() { return false; }

  componentDidMount() {
    // コンポーネントがViewportに入ったらHydrateする
    new IntersectionObserver(async ([entry], obbs) => {
      if (!entry.isIntersecting) return;
      obs.unobserve(this.root);
      const Child = await this.props.load();
      ReactDOM.hydrate(<Child />, this.root);
   }).observe(this.root);
  }

  render() {
    // bypass diffing のために必要（bypass diffing が何かはわかりませんでした…）
    return <section ref={c => this.root = c}
      dangerouslySetInnerHTML={{ __html: '' }} />;
  }
}
```

使うときは次のようにする

```jsx:
import hydrator from './hydrator';

...

<Hydrator load={() => import('./component')} />
```

* airbnbはこの方法でProgressive Hydrationを行っている
* AngularとVuewについても同様に説明
    * どちらもExperimentalながら実装している

**BotとSEOについて**

* CSRが負の影響を及ぼすのはJSをサポートしてないクローラーだけ
* Googlebotは以前はChrome 41でES6も新しいいくつものAPIもサポートしてなかった
* ついに最新のChromeがGooglebotに使われるようになった！
* 更に完全なSSRにしなくてもJSをサポートしていないクローラーには対応できる
    * metaタグとtitleタグを書くこと
    * 重要な情報はマークアップに含めること
        * react-snap や prerener-spa-plugin (vue) を使うと簡単にできる
    * UAがボットのときだけpre-rendered HTMLを返してもいいよ

**Data feching**

* prefetchしよう
* SSRのテクニックはリソース取得の「優先度付」や「能動的取得」にも同様に使える
* 回線速度によってリソース取得のタイミングを変えたりProgramaticallyなことをするときにとても便利

## Memo

* サイトをリッチにしたい…
* だけどFCPは遅くしたくない…
* という人向けの記事
* ブラウザのレンダリングプロセスの話だと思っていたが、HTMLをどう作るかという話だった
* Streaming SSRの活用例として引用されていたspectrumというサービスが良さそう
    * ReactやNuxt.jsといったコミュニティごとにざっくばらんな話をできるチャットサービス？
    * コミュニティごとにSlackチャンネルに入るのにうんざりしている自分のような人間にはぴったり
* 後半から字幕と音声がずれるようになってしまってイマイチ理解できなかった…