ハチャメチャに速いサイトをつくりたい
ということを思って、もうだいぶ前だけどhttps://surisuri.ninjaっていうのをつくった。半端になってるページもあるけど、やりたいことはざっとできたので趣味としては満足。Herokuで動いてます。ソースはこちら。いま仕事で開発してるサービスの公開APIで本家をマネする企画がことの始まり。
やったこと
CDNのエッジキャッシュを使う
まず前提として、HTMLは全部CDNにキャッシュする。後述するように動的に変わるコンテンツはキャッシュに載せなかったので、そういうコンテンツは表示されなかったとしても、ヘッダとか静的な部分まで表示されず画面真っ白というのはハチャメチャに速いとは言えない。なのでオリジンのサーバはなるたけコンテンツを載せたtext/htmlを返すようにする。ほぼ空っぽのHTMLに<script>
がぽつんとあるSPAみたいにはしない。
APIとフロントを分割する
いわゆるモノリシックなWebアプリにはしなかった。JSON APIとフロントが分かれていたほうが個人的に好みだし、チームで開発する場合も適していると思うのでそうした。ただそうなるとtext/htmlをつくるのはフロント側になるので、SSRする仕組みが必要になる。SSRをサポートしてくれるフレームワークはNuxtとかNextとかこの頃興味を持っていたSvelteのSapperなどがあるけど、Nextはpathパラメータの扱いが苦手そう?に見えたのでやめて、Sapperは後述するaタグでの遷移ができなかったのでやめてNuxtを選んだ。仕事でもVueを使っていて手に馴染んでるのでちょうどよかった。
普通のaタグを使う
普通じゃないaタグって何だよって感じだけど、SPA的な画面遷移をしないということ。リンクをタップしてからAPI叩いて必要な情報が揃ってから画面書き換え、ということをするよりも、CDNのエッジキャッシュに載ってるHTMLを取ってくる方が速いんじゃねーのと考えた。もちろん代替案はいくつも考えられて、
など。前者はちょっと記憶が曖昧だけど、Vueのmounted
を使えば遷移してからAPIを叩けるけど、そのページをSSRしたときにコンテンツが載ってこない。だからと言ってNuxtのasyncData
やfetch
を使うと、こいつらは遷移前に呼ばれるので、リンクをタップしてから遷移するまで時間がかかってしまう。そんな具合にサーバサイドとクライアントサイド両方でいい感じに叩く手段がなかったので、サーバサイドに振り切ったということ。後者は無駄な通信が発生する可能性がありギガに優しくないなと思ったのでやめた。
その他
CSSはheadタグの中にstyleタグで書いてCSSOMの準備が早くできるようにするとか、assetなどURLと内容が対応してて変化しないものはService WorkerでNetwork Firstなキャッシュをしてオフライン対応するとか、いくつか考えていたけど、この辺はNuxtがやってくれちゃったので頑張った感じはない。詳しくはhttps://github.com/nuxt-community/pwa-moduleなど参照のこと。
やらなかったこと
動的なコンテンツのキャッシュはしなかった。CDNによってはクッキーの値をキャッシュキーに含めることができるらしいけど、こちらが意図していない、また把握できないキャッシュをつくられる可能性があるのでやめた。なのでユーザごとのコンテンツやユーザによって作成・更新されるものはブラウザからAPIを叩いて取得する。dev.toなんかはそういうコンテンツもキャッシュに載せていて、ユーザによって更新されたタイミングでCDNのキャッシュを消している。なので絶対無理ってわけでは無いのだけど、これはこれで大変なのでやらなかった。
まとめ
そんなところで、趣味レベルだけど一度キャッシュに載ってくれればそこそこ速いものはできた。ただ例えばトップは時間によって内容が変わるので3時間でキャッシュ消すようにしてるので、キャッシュ切れててHerokuのdynoが寝てるときの描画はかなり遅いし、やっぱりAPI叩くところはどうしてもモッサリするので、もうひと押しって感じですね。