サイト高速化による罪滅ぼしと、SSRをしない勇気

[![top.gif](https://qiita-image-store.s3.amazonaws.com/0/89103/fbd43662-6e32-036b-bff9-616efdfa4677.gif)](https://houga.cc)

[高速化したサイト](https://houga.cc)

PHPで以前に作ったWebサイトを、
リニューアルついでにReact+Node.jsで書き直して、高速化を試みた一部始終です。

# まとめ
ファイルを圧縮して、gzipとHTTP/2で通信を最適化した後に、キャッシュを活用し、アニメーションを追加すれば、満足のいくスピードになる。

重たいWebフォントを読み込んですみません駆動開発。

# 対象のサイト
[邦画の予告を、朝まで](https://houga.cc)

という名前の、文字通り邦画の予告をひたすら観られるサイトです。

たまには邦画も悪くないですよ。

# 課題の把握
・初期表示にやたら時間がかかる
・ページ遷移の待ち時間が長く感じる
・ページ遷移時にバコってなる違和感

React+Node.jsでリニューアルした後、初期表示の遅さなどが原因で、体感的に遅くなってしまったのが高速化のきっかけです。

# 高速化の軸
> ユーザーを第一に考えます。最終目標は、特定の端末でのサイトの処理速度を上げることではありません。ユーザーが満足感を得ることが最終目標です。
>
[RAILモデルでパフォーマンスを計測する](https://developers.google.com/web/fundamentals/performance/rail)

やみくもに色々な方法を試しても終わりが見えないので、Googleのパフォーマンス改善のページを参考にしながら高速化を行いました。

長いものに全力で巻かれに行くスタンス。　

細かい最適化はせず、できる限り費用対効果が高そうなものに絞っていきます。

# 目次
**[不要なコンテンツの削除](#%e4%b8%8d%e8%a6%81%e3%81%aa%e3%82%b3%e3%83%b3%e3%83%86%e3%83%b3%e3%83%84%e3%81%ae%e5%89%8a%e9%99%a4)**
圧縮する前に不要なコンテンツを削除します。

**[リソースの圧縮](#%e3%83%aa%e3%82%bd%e3%83%bc%e3%82%b9%e3%81%ae%e5%9c%a7%e7%b8%ae)**
すべてのリソースをできる限り圧縮します。

**[通信の最適化](#%e9%80%9a%e4%bf%a1%e3%81%ae%e6%9c%80%e9%81%a9%e5%8c%96)**
gzipとHTTP/2で通信を最適化します。

**[ブラウザとCloudFrontのキャッシュ](#%e3%83%96%e3%83%a9%e3%82%a6%e3%82%b6%e3%81%a8cloudfront%e3%81%ae%e3%82%ad%e3%83%a3%e3%83%83%e3%82%b7%e3%83%a5)**
どんな圧縮もキャッシュにはかないません。

**[最小限のアニメーション](#%e6%9c%80%e5%b0%8f%e9%99%90%e3%81%ae%e3%82%a2%e3%83%8b%e3%83%a1%e3%83%bc%e3%82%b7%e3%83%a7%e3%83%b3)**
ラストワンマイルをアニメーションで補完します。

**[SSRをしない勇気](#ssr%e3%82%92%e3%81%97%e3%81%aa%e3%81%84%e5%8b%87%e6%b0%97)**
思考回路がショート寸前だったために断念しました。

# 不要なコンテンツの削除
> 最も時間がかからず、最も最適化されたリソースとは、送信されないリソースです。
>
[不要なダウンロードを回避する](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/eliminate-downloads)

哲学だ。

リニューアル前は、「心理テストに答えてあなたに合う邦画を見つけよう」みたいな恥ずかしいコンテンツがあったのですが、迷わず削除対象になりました。

フッターも要らないんじゃないかと思って一度削除したのですが、あった方がコンテンツの終了が明示的でわかりやすかったので、もとに戻しました。

[参考にしたページ](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/eliminate-downloads)では、リソースの一覧を作成して、それぞれが十分な価値を提供しているかを熟考しましょうと書いてありますが、全体のコンテンツが少ないのでこのぐらいで切り上げます。

# リソースの圧縮
**Webフォント >>>> JS >> 画像 > HTML**

外部リソースを除くと、↑の順にサイズが大きかったので、それぞれのサイズを削っていきます。CSSはJSに含まれています。

## Webフォントの最適化
文字は明朝体で表示したかったので[Noto Serif](https://www.google.com/get/noto/#serif-jpan)を選びました。ありのままでは絶望的な**23MB**なので、よく使う文字のみでサブセット化を行い、形式はWeb向けに最適化されたWOFFに変換しています。細かい方法は[詳しい記事](https://qiita.com/nowri/items/1c69b9b25f2958bd9f97)を参考にしていただくとして、

| 形式 | サイズ | 対象ブラウザ |
|:---:|:---:|:---:|
| WOFF2 | **1.4MB** | Chrome/Firefox |
| WOFF | **1.6MB** | IE9以上/Safari |

まで削減できました。第一水準漢字まで絞ればもっと軽くなるのですが、`濱田岳`さんなどの人名で第二水準漢字が結構使われているので、そこまで含めた結果がこのサイズです。

フォントの形式は、WOFF/WOFF2で大半のブラウザがカバーできるため、TrueTypeなどは[@font-face](https://developer.mozilla.org/ja/docs/Web/CSS/@font-face)の指定から外しました。古いブラウザでは、重たいWebフォントなんか読み込んでる場合じゃないと思うので。

[ウェブフォントの最適化](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/webfont-optimization)
[WOFF | MDN](https://developer.mozilla.org/ja/docs/Web/Guide/WOFF)
[TrueType | Wikipedia](https://ja.wikipedia.org/wiki/TrueType)
[OpenType | Wikipedia](https://ja.wikipedia.org/wiki/OpenType)

## JSの圧縮
Webpackによって生成される巨大なファイルを軽くしていきます。圧縮しないと**5.9MB**という目も当てられないサイズになるので、[uglifyjs-webpack-plugin](https://github.com/webpack-contrib/uglifyjs-webpack-plugin)に圧縮してもらいます。

```js
plugins: [
  new UglifyJSPlugin({
    ecma: 8
  })
]
```

これで **775KB**まで削れたのですが、このままだと本番環境では必要のない、各パッケージのワーニングやログが含まれている事があるので、環境変数に`production`を設定してそれらが出力されないようにします。

```js
plugins: [
  new webpack.DefinePlugin({
    'process.env': {
      NODE_ENV: JSON.stringify('production')
    }
  })
]
```

これで**571KB**まで減りました（まだ重い）。

> You shouldn’t apply UglifyJsPlugin or DefinePlugin with 'production' value in development because they will hide useful React warnings, and make the builds much slower.
>
[Optimizing Performance](https://reactjs.org/docs/optimizing-performance.html#webpack)

Reactのドキュメントに書いてあるとおり、開発中に`production`にしてしまうと色々と残念なことがあるので、本番環境にだけ適用するように設定します。Webpack実行時に読み込む設定ファイルを切り替えられるので、`webpack.common.js`に共通の設定を書いて、`webpack.dev.js`と`webpack.prod.js`にそれぞれの設定を書くのが良さそうです。共通部分は`merge`して使いまわします。

[詳しくはこちらで](https://webpack.js.org/guides/production/)

## 画像の圧縮
> 画像の最適化は芸術でもあり科学でもあります。
>
[画像の最適化](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization?hl=ja)

外部リソースを除くと、最適化されたFont AwesomeのSVGと、最適化されていないロゴのSVGしかなかったので、ロゴだけ目視で不要なタグを削除しました。pngやjpgの圧縮は効果が大きいですが、SVGはそれらに比べると元々軽いので、そこまで気にする必要はないかもしれません。

[Font Awesome 5](https://fontawesome.com)

## HTMLの圧縮
HTMLの生成にWebpackの[html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin)を使っているので、オプションで圧縮を有効にします。

```js
plugins: [
  new HtmlWebpackPlugin({
    minify: {
      html5: true,
      minifyCSS: true,
      collapseWhitespace: true,
      removeComments: true,
      removeEmptyAttributes: true
    }
  })
]
```

少しだけファイルサイズが減りました。

[うまく圧縮できないissue](https://github.com/jantimon/html-webpack-plugin/issues/346)

# 通信の最適化
リソースの圧縮の次に、通信の最適化を試みます。

## gzip
**サーバーで圧縮 → 圧縮したファイルを送る → ブラウザで展開**

サーバーでgzip圧縮を有効にすると、↑の処理が必要になるため多少のオーバーヘッドはありますが、ファイルの圧縮率が高い場合は通信時間がかなり短縮されるため、メリットの方が上回ります。

[GZIP によるテキストの圧縮](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer#gzip)

## HTTP/2
HTTP/2を使用すると、HTTPヘッダーの圧縮やリクエストとレスポンスの多重化が可能になるため、通信の高速化が望めます。

HTTP/1.xでは、リクエスト毎にコネクションを張らなければならず、大抵のブラウザではドメインに対して6つまでが上限のようなので、CSSスプライトやドメインシャーディングのような力技で最適化を行う必要がありました。

HTTP/2では、ひとつのコネクションで複数のデータをやりとりできるようなので、過去の最適化は不要になりそうです。

[HTTP/2の概要](https://developers.google.com/web/fundamentals/performance/http2/)
[Using Multiple TCP Connections](https://hpbn.co/http1x/#using-multiple-tcp-connections)
[Optimizing for HTTP/1.x](https://hpbn.co/optimizing-application-delivery/#optimizing-for-http1x)

## フロントエンド
フロントエンドはCloudFront+S3の構成なので、CloudFrontの管理画面から設定を変更するだけでgzip圧縮とHTTP/2を有効にできました。こちらのブログがとてもわかりやすかったです。

[AWSのCloudFrontからコンテンツをHTTP2 & gzipで高速に配信する](http://cluex-developers.hateblo.jp/entry/cloudfront-with-http2-and-gzip)

gzipを有効にした結果、**571KB**あったJSのサイズが**162KB**になり、70%ぐらい削減できました。通信量が減ると請求が安くなるのも地味に嬉しいです。Webpackするときにgzipしようか迷いましたが、CloudFrontはgzipしたリソースもキャッシュしてくれるようなので、任せることにしました。

[圧縮ファイルの供給](http://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html)

## サーバーサイド
> 圧縮を適用する最適な方法は、リバース・プロキシー・レベルで実装することです。その場合は、compressionミドルウェアを使用する必要はありません。
>
[実稼働環境におけるExpressの使用におけるパフォーマンスに関するベスト・プラクティス](http://expressjs.com/ja/advanced/best-practice-performance.html#use-a-reverse-proxy)

サーバーサイドはNode.jsの前にNginxを置いているので、設定ファイルでgzipとHTTP/2を有効にします。Expressのミドルウエアでも手軽にgzipできますが、ベストプラクティスに従ってNginxに任せます。

```nginx
# Nginx 1.11
server {
  # HTTP/2
  listen 443 ssl http2;

  # gzip
  gzip on;
  gzip_types application/json;
}
```

HTTPS対応は、Let's Encryptまわりの作業を楽にしてくれる[Certbot](https://certbot.eff.org/)を使えば、環境が対応している場合は、証明書の取得から設定ファイルの編集までいい感じにやってくれます。細かい設定は追加で行う必要がありますが、だいぶ楽になります。

できればサーバーサイドも、API Gatewayなどでキャッシュを効果的に使った方が良さそうですが、思考回路がショート寸前だったため、手をつけていません。設定ファイルの見直しも含めてそのうちやりたい・・・

[gzip](http://nginx.org/en/docs/http/ngx_http_gzip_module.html)
[HTTP/2](https://www.nginx.com/blog/nginx-1-9-5/)
[Nginx on CentOS/RHEL 7](https://certbot.eff.org/#centosrhel7-nginx)
[APIキャッシュを有効にして応答性を強化する](http://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-caching.html)

# ブラウザとCloudFrontのキャッシュ
> 前に取得したリソースをキャッシュに保存して再使用できることは、パフォーマンスを最適化する上で非常に重要です
>
[HTTPキャッシュ](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#no-cacheno-store)

圧縮は一通り終わりましたが、まだキャッシュを活用できていないので設定していきます。なにかと忌み嫌われがちなキャッシュですが、正しく向き合えばきっと味方をしてくれるはず。

CDNとCloudFrontの表記が混在するとややこしいのと、他のCDNを使ったことがなく、仕様がCloudFront特有のものかがわからなかったため、少し長いですがCloudFrontに統一しています。

## ブラウザのキャッシュ
最も強力で、最も厄介なキャッシュです。
データがクライアントに保存されるので、余計な通信が発生しない理想的な状態。意図せずにキャッシュされてしまうと少し面倒なので、強い意志を持って計画的に利用する必要があります。

## CloudFrontのキャッシュ
ブラウザにキャッシュしたくない場合も、CloudFrontにキャッシュすれば素早くレスポンスを返せます。ハッシュでバージョニングしていないリソースを更新した場合は、キャッシュの無効化が必要です。

[オブジェクトを無効化するか、バージョン付きオブジェクト名を使用するかの選択](http://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html#Invalidation_Expiration)

## Cache-Controlに何を設定すべきか
Cache-Controlの仕様をイマイチ理解していなかったので、RFCを参考に使いそうなものをまとめました。

| ディレクティブ | 挙動 |
|:---:|:---:|
| no-store | 一切キャッシュしない |
| no-cache | キャッシュするけど毎回更新を確認 |
| max-age | 指定した秒数キャッシュ |

### no-store

> The "no-store" request directive indicates that a cache MUST NOT store any part of either this request or any response to it.
>
> [RFC 7234 HTTP/1.1 Caching 5.2.1.5. no-store](https://tools.ietf.org/html/rfc7234#section-5.2.1.5)

いかなるキャッシュも保持しない。強気。

### no-cache

> The "no-cache" request directive indicates that a cache MUST NOT use a stored response to satisfy the request without successful validation on the origin server.
>
> [RFC 7234 HTTP/1.1 Caching 5.2.1.4. no-cache](https://tools.ietf.org/html/rfc7234#section-5.2.1.4)

リソースの更新を確認をせずに、キャッシュを使ってはいけない。リソースに更新がない場合は、[304 Not Modified](https://developer.mozilla.org/ja/docs/Web/HTTP/Status/304)が返ってくるので、そのままキャッシュを使用する模様。

とはいえキャッシュはしている状態なので、文字通りの`no-cache`ではない気がする。

### max-age

> The "max-age" request directive indicates that the client is unwilling to accept a response whose age is greater than the specified number of seconds.
>
> [RFC 7234 HTTP/1.1 Caching 5.2.1.1. max-age](https://tools.ietf.org/html/rfc7234#section-5.2.1.1)

指定した秒数以上に時間がたったキャッシュは使用しない。指定した秒数の間はキャッシュを使用するとも言える。時間を指定して積極的にキャッシュしたい場合に使うのが良さそう。

> If a response includes a Cache-Control field with the max-age directive, a recipient MUST ignore the Expires field. 
>
> [RFC 7234 HTTP/1.1 Caching 5.3. Expires](https://tools.ietf.org/html/rfc7234#section-5.3)

Expiresよりも強い上に、キャッシュを日時で指定することは今のところないので、キャッシュするコンテンツはmax-ageで統一したい気持ち。

## ブラウザとCloudFrontのキャッシュを合わせて使う

指定方法の組み合わせが多いため割りとめんどくさいですが、CloudFrontのドキュメントにある一覧表を参考に設定方法を確認していきます。

[CloudFront がキャッシュにオブジェクトを保持する期間の指定](http://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/Expiration.html#ExpirationDownloadDist)

### ブラウザとCloudFrontの両方にキャッシュする場合
**S3でmax-ageを指定**

最小TTLや最大TTLの設定によって適用される秒数が変わりますが、`最小TTL < max-age < 最大TTL`の場合は、max-ageに指定した秒数が、ブラウザとCloudFrontに適用されます。

TTL：キャッシュが有効な秒数。デフォルトと最小/最大を設定します。Time To Live。

### ブラウザにキャッシュせず、CloudFrontにキャッシュする場合
**S3でno-storeを指定して、CloudFrontで最小TTLを設定**

S3のリソースのCache-Controlに`no-store`を指定して、CloudFrontで最小TTLが設定されている場合は、ブラウザにはキャッシュされず、最小TTLの間だけCloudFrontにキャッシュされます。

### ブラウザとCloudFrontのキャッシュ時間を個別に設定する場合
> The "s-maxage" response directive indicates that, in shared caches, the maximum age specified by this directive overrides the maximum age specified by either the max-age directive or the Expires header field.
>
> [RFC 7234 HTTP/1.1 Caching 5.2.2.9. s-maxage](https://tools.ietf.org/html/rfc7234#section-5.2.2.9)

個別にキャッシュ時間を設定する場合は、`max-age`にブラウザ、`s-maxage`にCloudFrontの秒数を指定します。`s-maxage`はCDNに限った話ではなく、もっと広い意味でプロキシサーバーにおいて`max-age`の指定をオーバーライドするディレクティブのようです。なんだか直感的に嫌な匂いがするので、できることなら近づきたくない印象。

そんなこんなで、今回は**no-storeとmax-ageのみ**を使用することにしました。

## 何をキャッシュして、何をキャッシュしないか

| 　 | ブラウザ | CloudFront |
|:---:|:---:|:---:|
| HTML | × | ◯ |
| JS | ◯ | ◯ |
| Webフォント | ◯ | ◯ |
| API | × | - |

参考：[SPA と S3 と CloudFront とキャッシュ](https://qiita.com/iktakahiro/items/87a488a39e156898ea41)

### HTMLのキャッシュ
**Cache-Control: no-store**

index.htmlはブラウザにキャシュして欲しくないのでCloudFrontにのみキャッシュするように設定します。

最小TTLに半年を設定しているため、ブラウザにはキャッシュされず、CloudFrontに半年キャッシュされます。リソースの更新時には、CloudFrontのキャッシュを無効化します。現状は週一ぐらいで更新しているので、半年フルでキャッシュされることはまずありません。

### JSのキャッシュ
**Cache-Control: max-age=31536000**

※ 31536000 = 365日

HTMLでJSを読み込む際にハッシュを付与してバージョニングしているため、ブラウザ/CloudFrontの両方にキャッシュさせるように設定します。JSが更新された場合、`bundle.js#c58kamakura1185bf`のようにハッシュが変わるため、最新のリソースが読み込まれます。HTMLの生成に、[html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin)を使用している場合、オプションで有効にできます。

```js
plugins: [
  new HtmlWebpackPlugin({
    hash: true,
  })
]
```

### Webフォントのキャッシュ
**Cache-Control: max-age=31536000**

Webフォントは基本的に変更しないため、ブラウザ/CloudFrontの両方にキャッシュします。

### APIのキャッシュ
気力がなかったので断念しました。

## キャッシュの設定を含めて本番化
```bash
aws s3 cp ./dist/index.html s3://houga.cc/index.html --cache-control no-store
aws s3 cp ./dist/bundle.js s3://houga.cc/bundle.js --cache-control max-age=31536000
aws cloudfront create-invalidation --distribution-id hoge --paths / /index.html
```
AWS CLIのコマンドで、キャッシュの設定をオプションで指定してS3へファイルをコピーし、index.htmlのオブジェクトキャッシュを無効化します。JSに変更があった場合は、ハッシュが変わるので新しいものが読み込まれます。

[s3 cp](http://docs.aws.amazon.com/cli/latest/reference/s3/cp.html)
[cloudfront create-invalidation](http://docs.aws.amazon.com/cli/latest/reference/cloudfront/create-invalidation.html)
[AWS コマンドラインインターフェイス](https://aws.amazon.com/jp/cli/)

# 最小限のアニメーション
キャッシュや圧縮をいくら頑張っても縮められる時間には限界があるので、そこのところをアニメーションで補います。SPAにおける[ラストワンマイル](https://www.amazon.co.jp/%E3%83%A9%E3%82%B9%E3%83%88%E3%83%AF%E3%83%B3%E3%83%9E%E3%82%A4%E3%83%AB-%E6%96%B0%E6%BD%AE%E6%96%87%E5%BA%AB-%E6%A5%A1-%E5%91%A8%E5%B9%B3/dp/4101335745)を補完する、とても大事なところです。

**ラストワンマイル**
通信回線や物流における最後の1マイル。事業者からユーザへ届ける最後の工程で、重要で手間がかかる部分ぐらいの意味。

## アニメーションに適切な時間
> アニメーションの継続時間は、ease-outとease-inは200～500ミリ秒に、bounceとelastic easeは、800～1200 ミリ秒に設定します。
> 
> [適切なイージングの選択](https://developers.google.com/web/fundamentals/design-and-ux/animations/choosing-the-right-easing)

ユーザが待ちを感じず、かつアニメーションの意味を理解できる秒数を考えると、300ms前後がちょうど良さそうです。アニメーションの種類や量によって変わってくると思うので、あくまで参考程度に。

## どのプロパティを使うか
> アニメーションの形状と不透明度の変更のみを行うようにします。
> 
> [コンポジタ専用プロパティの優先使用、およびレイヤー数の管理](https://developers.google.com/web/fundamentals/performance/rendering/stick-to-compositor-only-properties-and-manage-layer-count?hl=ja)

まわりのレイアウトに影響を及ぼすプロパティの変更はコストが高いので、アニメーションするのは`transform`と`opacity`に絞ったほうが良さそうです。さらにアニメーションを最適化するためには、要素上で予測される変更を、`will-change`プロパティで事前に宣言しておく必要があります。

```css
.box {
  will-change: transform, opacity;
}
```

ただしこの最適化は、ブラウザに相応のコストを強いることになるので、アニメーション開始前後でオン/オフ切り替えを行うなどして、ブラウザを気遣う姿勢が求められます。

> ページが良く動作している場合、少々のスピードアップのためだけに will-change プロパティを要素に追加しないでください。 
>
> [will-change](https://developer.mozilla.org/ja/docs/Web/CSS/will-change)

良く動作しているかは置いておいて、今回のケースではそこまでクリティカルなアニメーションがないのと、端末のリソースを不必要に消費するのは本意ではないので、`will-change`は使わないことにしました。

ここら辺を調べていくと、ブラウザではスタイルが変更されると、レイアウトしてペイントして、コンポジットほにゃららがふがふがみたいな話になってくるので、ざっくり知りたい方はこちらで。

[レンダリングパフォーマンス](https://developers.google.com/web/fundamentals/performance/rendering/?hl=ja)

## CSSかJSか、それが問題だ
> ブラウザがメインスレッド上で負荷の高いタスクをいくつか実行している場合でも、これらのアニメーションは中断されることなく続行できます。
>
> [アニメーションとパフォーマンス](https://developers.google.com/web/fundamentals/design-and-ux/animations/animations-and-performance)

CSSのアニメーションは、JavaScriptが実行されるブラウザのメインスレッドとは別で動作するようなので、よほど複雑なアニメーションでない限り、JSではなくCSSでアニメーションを行ったほうが良さそうです。（ブラウザによって実装が違うかもしれないので、一概に言えませんが）
　
　
以上のことをまとめると、**CSSを使用して、transformとopacityを300ms前後でアニメーションさせる**というのが、アニメーションの指針になります。

色々調べましたが、ローディングとページ遷移にしか大きなアニメーションは使用しないので、少しやりすぎた感があります。

## 読み込み中のローディング
　
![iwayuru_loading.gif](https://qiita-image-store.s3.amazonaws.com/0/89103/8df08abc-d1e7-f84e-54b4-d0357ad524e0.gif)

いわゆるローディングをいれるのもありですが、頻繁にぐるぐる回られると、待たされてる感が助長されてしまう気がするので、よく見たらなんかやっとるな程度のアニメーションにしました。スマホのChromeやSafariで読み込み時に出てくるやつみたいな感じです。

![loading.gif](https://qiita-image-store.s3.amazonaws.com/0/89103/05052ae9-7bb6-9ccb-3ebf-21e33367bdaf.gif)

ヘッダーのボーダーに黒い線を重ねて、ロードが完了したらサッと流れるようにして、なにかやっている感を出しています。3秒で70%まで行くスピードでゆっくり進んで、ロードが完了したらサッと流れながら消える、進捗バーとは名ばかりの適当な実装です。

```scss
// $easeOutCubic => イージングの変数
// @include min => メディアクエリのmixin
.header-loading {
  transform: scaleX(0);
  transform-origin: left center;
  &.is-fetching {
    transform: scaleX(.7);
    transition: 3s $easeOutCubic;
  }
  &.is-fetched {
    transform: scaleX(1);
    opacity: 0;
    transition: .3s $easeInOutCubic;
    @include min {
      transition-duration: .5s;
    }
  }
}
```

命名に関しては、BEM（というか[MindBEMding](https://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/)？）を見ると拒否反応が起きてしまうアレルギーを持っているので、TrelloのCSSガイドラインを参考にしました。考えることが少なくシンプルでよいです。

[【超訳】 Trello CSS Guide](https://qiita.com/naru0504/items/5427f3795171c540ed91)

## ページ遷移
特に何も意識せずに実装すると、ページ遷移時にコンテンツが切り替わってバコッとなってしまい、唐突な感じが出てしまうので、申し訳程度のアニメーションを追加します。

スマホのように横に遷移するアニメーションや、クロスフェードするアニメーションもやってみましたが、やり過ぎ感が出てしまったので、フェードインのみのアニメーションに落ち着きました。

![fade.gif](https://qiita-image-store.s3.amazonaws.com/0/89103/64853f89-963f-7129-06d9-a2ac5c2ded9b.gif)

```scss
.motion-fade {
  opacity: 0;
  &.is-active {
    opacity: 1;
    transition: opacity .25s linear;
  }
}
```

# SSRをしない勇気
高速化を掲げるからには避けては通れないところだと思いますが、勇気をもってSSRはしないという選択をしました。勇気とはいったい。

**SSRについて**
ブラウザでJSが実行されるまでページが空なのが嫌なら、サーバーサイドでレンダリングして返せばいいじゃんという話。

## 思考回路はショート寸前
ここにきて頭のリソースが枯渇しつつあったので、SSRは費用対効果を考えてやる気が起きませんでした（言い訳）。S3が使えなくなってしまうのも痛い。「SSRしたら人生変わりましたーーー！」と叫びながら金属バットでケツでも叩かれない限り実装できない気がします。

そんなこんなで最初からSSRをする気力は残っていなかったのですが、なんだか寂しいので見苦しい言い訳をだらだらと書いていきます。

## SSRをしたら本当に速くなるのか
> **ページの作成にJavaScriptフレームワークを使用している場合はどうすればよいですか？**
>
> サーバーサイドレンダリングを使用すると、最初のページの読み込みのパフォーマンスが大幅に改善される場合があります。
>
> [レンダリングを妨げるJavaScriptを削除する](https://developers.google.com/speed/docs/insights/BlockingJS?hl=ja)

こればかりは実際にSSRをしてみないとわからないのですが、JSでレンダリングした後のHTMLを手動で貼り付けて、PageSpeed Insightsで点数を比較してみました。

本来はサーバーでレンダリングする処理が入りレスポンスが多少は遅くなると思うので、全く意味をなさない作業ですが、暇だったので比べてみます。

### 現状
　
![kanashi.jpg](https://qiita-image-store.s3.amazonaws.com/0/89103/73212ff8-fcc0-c58a-4fc3-818d5ec34ffb.jpeg)
　
**60点**

サイトへアクセス後、JSが実行されるまで何もレンダリングされないため、悲しくなる数字になりました。Googleは[クリティカルレンダリングパス](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/?hl=ja)をかなり重視しているようです。

### HTMLのみにする
　
![munashi01.jpg](https://qiita-image-store.s3.amazonaws.com/0/89103/36bc0939-8388-a471-7a7e-48d361b1645b.jpeg)
　
**78点**

JSを実行せずに静的なHTMLを表示しているだけなので、
かなり改善しましたが、まだ満足な数字ではありません。

### キャッシュ期間が短い外部リソースを削除
　
![munashi02.jpg](https://qiita-image-store.s3.amazonaws.com/0/89103/a82566f0-d19a-75bc-867c-b2b04cf2ad49.jpeg)
　
**100点**

外部リソースのキャッシュ期間が短かったので、試しに削除してみると、文句のない数字になりました。ここまでくると、もはやSSRは関係ありません。

> ユーザーを第一に考えます。最終目標は、特定の端末でのサイトの処理速度を上げることではありません。ユーザーが満足感を得ることが最終目標です。
>
[RAILモデルでパフォーマンスを計測する](https://developers.google.com/web/fundamentals/performance/rail)


## 初期描画が遅い問題は受け入れる
JSが読み込まれてレンダリングされるまで初期描画がされないため、SSRで少しでも早く表示した方が良いのは間違いないと思いますが、圧縮後のJSが**150KB**程度だったのでいけると判断しました（適当）。受け入れるというと聞こえが良いですが、単なる妥協ともとれます。

## GoogleがJSを実行してくれた
> we decided to try to understand pages by executing JavaScript. It’s hard to do that at the scale of the current web, but we decided that it’s worth it. 
>
> [Understanding web pages better](https://webmasters.googleblog.com/2014/05/understanding-web-pages-better.html)

SEOまわりに疎かったので、GoogleがJSを実行してくれるというのは都市伝説だと思っていたのですが、Search Consoleで確認したところ、無事にJS実行後のページを認識してくれているようでした。Googleの勇気ある決断がすごい。

![fetch_as_google.jpg](https://qiita-image-store.s3.amazonaws.com/0/89103/d3252076-92cd-aaa5-0436-82bb92ebd67e.jpeg)

Search Consoleの`クロール > Fetch as Google`から表示を確認できます。
そもそもサイトの情報量が少なく、SEO的には残念なページしかないのでどちらでも良かったのですが、クローラから見えているようでひと安心です。（正しくインデックスしてくれているかは知りません）

[ウェブサイト用Fetch as Googleを使用する](https://support.google.com/webmasters/answer/6066468?hl=ja)
[Google クローラ](https://support.google.com/webmasters/answer/1061943?hl=ja)
[Googlebot](https://support.google.com/webmasters/answer/182072)

## OGP取得できない問題は気にしない
TwitterやFacebookのクローラは、JSを実行せずにHTMLをパースしているだけっぽいです（推測）。Googleに比べると、クロールの重要度は低いと思うので当たり前かもしれませんが、少し悲しい気持ちになりました。

ですが、シェアされる事はないだろうという後ろ向きな気持ちを盾に、OGPはトップページにのみ設定しています。そのうちクローラが対応したら変更します。
　
　
ここまで否定的なことを書いてきましたが、すべては実装する能力がないために言い訳を探しただけなので、SSRを実戦投入している人たちのことは心から尊敬しています。どういう頭の構造してるんだろう。
　
長くなりましたが、SSRをしなかった言い訳は以上です。

[永い言い訳 | 邦画の予告を、朝まで](https://houga.cc/movie/12)

# 贖罪としての高速化
一連の高速化を終えて振り返ると、明らかにおかしいのが**1.2MB**あるWebフォントです。

キロを削る努力をしてきたのに、堂々たるメガ。

10円単位の節約はするのに、1000円単位で散財してしまう、典型的な節約ベタのそれに近い状態です。

どうしても、邦画のタイトルを表示するのにゴシックはなんだかなぁという思いがあり、何回かWebフォントをやめてみたものの腑に落ちなかったので、わがままを通した結果がこれです。

最初はサイトの表示を速くするために高速化を行っていたのですが、途中からは重たいWebフォントの読み込みに対して、勝手に罪の意識を感じて少しでもそれを軽減したいという一心でサイズを削っていました。

AndroidでWebフォントを使わずに綺麗な明朝体を表示できる時代は来るのだろうか。

# 課題は解決できたのか
**初期表示にやたら時間がかかる**
Webフォントが重い&SSRできていないので解決には至らず

**ページ遷移の待ち時間が長く感じる**
通信の最適化とアニメーションのおかげでそこまでストレスはなくなった

**ページ遷移時にバコってなる違和感**
バコッとはならなくなったので良しとする

リニューアル前のコードを、衝動的にGitのリポジトリごと削除してしまったので比較はできませんが、体感的には速くなっている気がします。もろもろ妥協した箇所があるのでスッキリはしませんが。

高速化を試みる記事の体裁としては、最後にPageSpeed Insightsが100点になったスクショでも貼って、こんなに速くなりました！としたいところですが、釈然としない結果で不甲斐ない。

**60点**

# 今後必要なこと
## コンテンツの充実
どれだけ高速化に力を入れても、コンテンツがしょーもなかったら意味がないので、質・量ともに充実させていかないとマズいなぁという焦りがあります。ふと我に返ると、誰が使うんだこのサイト、と思ってしまうので。まずは邦画をたくさん観る。

## APIのレスポンスを速くする
サーバーサイドの実装は改善の余地しかないのでどうにかしたい・・・

## アプリを作る
スマホの使い勝手は、React Nativeでアプリを作ったらいくらかマシになるんじゃないかという幻想を抱いています。現状はナビを下に置いていて、Safari「2タップ」問題が気になるのですが、アプリにすれば問題なくなるはず。

![safari_2tap.gif](https://qiita-image-store.s3.amazonaws.com/0/89103/a7e2f377-ccd3-23a9-2a56-50faa4236cb1.gif)

**Safari「2タップ」問題**
Safariの下のほうをタップすると、アドレスバーが出てしまうので2タップ必要になる問題。すでにアドレスバーが出ている場合は問題にならない。

```
// TODO: スワイプで戻ると大幅にレンダリングが遅れるのに今さら気がついた
```

実家に帰ったとき、ハンバーガーメニューがビヨンと開くのを見た母が、「へぇ～これはわかんないよ〜」とぼそっと言っていたのが、まだ耳に残っています。スマホのナビに最適解はあるのだろうか。

[邦画の予告を、朝まで](https://houga.cc)
 
 
おわり

# 参考
[「思考回路はショート寸前」とは、どういう意味か](http://sakuraitimo.com/%E3%80%8C%E6%80%9D%E8%80%83%E5%9B%9E%E8%B7%AF%E3%81%AF%E3%82%B7%E3%83%A7%E3%83%BC%E3%83%88%E5%AF%B8%E5%89%8D%E3%80%8D%E3%81%A8%E3%81%AF%E3%80%81%E3%81%A9%E3%81%86%E3%81%84%E3%81%86%E6%84%8F%E5%91%B3-701.html)