初めに
こんにちは、IESHILでエンジニアをしているgccjです。
IESHILはマンション査定価格がいますぐわかるサービスです。
2021年4月6日にその中でも一番アクセスされている建物詳細ページをISR(Incremental Static Regeneration)化しました。
それについて、目次の順番でご紹介していきたいと思います。
目次
- 前提--SST, CSR, SSR, SSGの説明
- 既存のシステム構成
- 既存のシステム構成における課題
- 建物詳細ページをISR化した理由
- 建物詳細ページをISR化した結果
- 建物詳細ページをISR化した後のシステム構成
- 最後に
前提--SST, CSR, SSR, SSGの説明
まず、「そもそもISRって何?」という説明はこの後の ISRをした理由で触れる予定です。
一方、それを理解するために、
- Server-Side Templating(以下、SST)
- Client-Side Rendering(以下、CSR)
- Server-Side Rendering(以下、SSR)
- Static Site Generating(以下、SSG)
についての理解が必要です。
Web系のエンジニアであればご存知の内容とは思いますが、超簡単な説明だけさせていただきます。
まず、ユーザーに表示するコンテンツ(完全なHTML)をレンダリングする場所によって、SSTとCSRに分けられます。
レンダリング方式 | レンダリング場所 | 技術例 | 主なメリット | 主なデメリット |
---|---|---|---|---|
SST | サーバー | Rails + ERB | - SEOに有利 | - インタラクティブなページに向いていない |
CSR | ウェブブラウザ | React | - インタラクティブなページに向いている | - SEO不利 - レンダリング速度がクライアント側の端末性能に左右される |
そして、SSRは厳密に言えば、SST+CSR(Universal Rendering)になりますが、
現在のところ、一般的にはサーバー側がCSRまで担当することを指すようです。
レンダリング方式 | レンダリング場所 | 技術例 | 主なメリット | 主なデメリット |
---|---|---|---|---|
SSR | サーバー | Next.js | - SEOに有利 - インタラクティブなページに向いている |
- TTFBが遅い - 専用サーバーが必要 |
また、SSGは、サーバーサイドでレンダリングするのですが、
SST、CSR、SSRではクライアントからのリクエストごとにレンダリングするのに対し、
SSGは事前に全てのページをレンダリングします。
何回アクセスされても事前にレンダリングされたページを再利用して、クライアントに返します。
リクエストごとにレンダリングする必要がないため、ページスピードがとにかく速いです。
一方、事前に全てのページをレンダリングする必要があるため、下記の問題もあります。
- ページ数が多ければ多いほどレンダリングコストが大きくなります。
- データの更新が必要となる場合、全てのページを再レンダリングする必要があります
レンダリング方式 | レンダリング場所 | レンダリングタイミング |
---|---|---|
CSR SST・SSR |
ウェブブラウザ サーバー |
リクエストがある度 |
SSG | サーバー | 事前、通常WebAppをbuild時 |
*より詳しい説明はインターネット上にある記事をご参照ください。
既存のシステム構成
IESHIL自体はRuby on Rails(以下、Rails) 4系から進化し続けてきました。 今回建物詳細ページがISR化される前までの簡単なシステム構成は下記になります。
一言で言うと、 建物詳細ページを含む全てのページでSST(Rails + Slim)とCSR(React)が混在していました。Rails+Slim(サーバー側)でページのベースとなる部分をレンダリングした後、React(クライアント側)で残りのパーツをレンダリングする形となっていました。
既存のシステム構成における課題
この構成に伴う私たちが直面していた課題として、UX(ユーザー体験)観点とDX(開発者体験)観点とそれぞれ説明します。
UX観点
1. ページスピードが遅い
先ほども触れたとおり、SSTがベースになっているため、クライアントからのリクエストが都度サーバーに到達します。
その結果、TTFBがより遅くなります。
さらに、CSRが必要なため、トータルでユーザーにページが表示されるまで遅かったです。
2. CLS(Cumulative Layout Shift 画面のガタツキを表す指標)が高い
SSTでレンダリングされたベースの上でCSRを行うため、クライアント側で描画した後に画面のガタツキが多く(CLSが高い)発生していました。
3. 価値をユーザーに届けられない恐れ
CSRの際には必要なデータをWeb APIから取得していました。
そのため、クローラーが来た際に、クロールバジェット(単一のサイトに対するクローラーのクロール上限のような物)が消費されることになります(IESHILの場合70%ほど)。
いわゆる、CSRで発生するデータ通信がクローラーのIndex率を低下させています。
その結果、本来ユーザーに提供できる価値が検索結果に出てこない恐れがあります。
DX観点
1. 開発コストが高い
一つのページに、SSTのパーツとCSRのパーツが混在していました。
一つのユーザー価値を提供するために、この辺りがRailsであの辺りがReactで、といったように二つのアプリを修正する作業が発生しています。
例えば、パーツ表示順番の切り替えだけでも、パーツの粒度により、修正が大変な時がありました。
2. ウェブブラウザごとの差分への対応
CSRの場合、クライアント側が利用するウェブブラウザで無事に実行できるように担保する必要があります。
また、CSRの部分が多いほど、対応コストが増えていくことになります。
現時点、IESHILユーザーの中で、IE11の利用がまた一部があるため、対応が求められます。
3. 開発者のモチベーション
エンジニアとして、何か新しい仕組みや技術を取り入れれば、モチベーションがあがりますので、常に新しいことをチャレンジしたいです。
建物詳細ページをISR化した理由
上述した課題を解決するため、私たちは色々な対策を探りました。 結果としてIncremental Static Regeneration(増分静的再生、以下はISRと略)にシフトすることにしました。
ISRにシフトしたい理由
ISRはNext.js 9.5より正式リリースされたSSRをベースとした仕組みです。
SSGと同じく、レンダリングしたページを再利用するのですが、
一定期間でSSRで再レンダリングすることでデータの更新性を担保しつつ、SSGのような事前に大量のページのレンダリングを必要としないことが特徴です。
レンダリング方式 | レンダリング場所 | レンダリングタイミング | 技術例 |
---|---|---|---|
ISR | サーバー | 一定期間内の初めてのリクエスト | Next.js |
話をもう少しわかりやすくするために、この一定期間をたとえば60s
として、IESHILビル
という建物詳細ページに対するリクエストがあったときのサーバー側の処理を分解して見てみましょう。
No. | リクエストタイミング | 処理 | 備考 |
---|---|---|---|
1. | 初めてのリクエスト | IESHILビルのページ(v1)をレンダリングする | - この時点まではSSRと同等 - 説明するため、tag v1を付ける |
2. | 初めてのリクエスト | IESHILビルのページ(v1)をCacheする | |
3. | 初めてのリクエスト | CacheされたIESHILビルのページ(v1)を返す | |
4. | その以後の60s以内に来たリクエスト | 3と同じCacheされたIESHILビルのページ(v1)を返す | |
5. | 60sに来たリクエスト | CacheされたIESHILビルのページ(v1)を返す | 3と同じ |
6. | 60sに来たリクエスト | IESHILビルのページ(v2)を新しくレンダリングする | - 説明するため、tag v2を付ける |
7. | 60sに来たリクエスト | IESHILビルのページ(v2)をCacheする | |
8. | 60s以降次の60s以内にリクエスト | CacheされたIESHILビルのページ(v2)を返す |
仕組みの要件として、一定期間内で下記の2つを実行する必要がありました。
- 一回レンダリング(SSR)したものをCacheして再利用すること。
- バックグラウンドでCacheを更新すること。
気付きましたか?
実はこれがstale-while-revalidate
というキャッシュ戦略と同じ考え方です。
ゆえに、SSRできるフレームワークとstale-while-revalidate
をサポートしているCDNを使えば、Next.js以外の技術でもISRを実現可能です。
また、ISRを実装する場合 Vercel を使うのが一般的ですが、IESHILでは HerokuとFastly(stale-while-revalidate
対応)を使っていたため、今回の実装もHerokuとFastlyを利用しました。
それではこの仕組みはどのように私たちの課題解消できるのかを説明します。
同じくUXとDXの観点から説明します。
UX観点
1. ページスピードを高速化
(一定期間内で)Cacheをそのまま返すため、都度SSRするコストが防げます。
また、都度SSRが必要ないため、CDNを利用すれば、(CDNのエッジ)サーバーとクライアントとの物理的距離を短くすることもできます。
そのため、TTFBを大幅に減らせます。
2. CLSを低減
個人情報などセンシティブな情報以外、ほぼ全てのコンテンツがSSRされるため、CLSが最小限に抑えられます。
センシティブな情報が入っていないページ(IESHILで例えば、未ログインの建物詳細ページとか)の場合、CLSを0.000にすることができます。
3. より多くの価値を届ける可能に
個人情報などセンシティブな情報以外、ほぼ全てのコンテンツがSSRされるため、CSRでAPIから取る必要なデータが大幅に減らせます。
結果、それによるクロールバジェットの消費も抑えられ、クローラーのIndex率を向上させ、より多くのIEHSILページが関連の検索結果に出るようになります。
DX観点
1. 開発コストを削減
SSRにすることで、全部Js(今回の場合、Next.jsになる)に統一されます。
最終的に、一つのユーザー価値を提供するため、二つのアプリを開発するようなことがなくなります。
2. ウェブブラウザごとの差分への要対応箇所を削減
ほぼ全てのコンテンツがSSRされるため、CSRの割合を縮小することができます。
それによって、ウェブブラウザ対応もより楽になります。
3. 開発者のモチベーションを向上
ISRと言う新しい仕組み、Next.jsもチーム内初採用、ウェブブラウザ対応が楽になるなどなど、全て開発者のモチベーションにプラスになります。
建物詳細ページにする理由
上でご紹介したようにISRにすることでUXとDXの両方に良い影響があります。
サイト全体をISR化できれば、もちろん一番望ましいかもしれません。
一方、それを実現するのに、エンジニアリソースが必要である上に、開発時間も伸びざるを得ないことになります。
なので、私たちは大きな課題を分割し、成果を継続して出していくことを選びました。
その結果として、一番アクセスされている、IESHILがサービスとして提供しているメイン価値でもある建物詳細ページをISR化しました。
建物詳細ページをISR化した結果
今回建物詳細ページをISR化した後、周囲に「え〜!ページを開くのを感じられないぐらい速いなあ!」といった声をたくさん貰いました。
定性的な指標として、嬉しく思っています。
一方、建物詳細ページの定量的な数値も見てみましょう。
*モバイルでの指標
Google Serach Consoleのページ エクスペリエンス指標
その他の指標
指標 | Before | After |
---|---|---|
TTFB | 約数百ms前後 | 約十ms前後 |
LCP | 約数秒前後 | 約0.数秒前後 |
CLS | 約0.500前後 | 約0.000 |
優良URL件数 | 約16倍UP | |
良好URL表示回数/日 | 約2倍UP | |
Lighthouseのパフォーマンススコア | 40~50程度 | 70~80程度 |
*2021/04/30時点のデータ。改善やISR移行に伴う数値が変更する場合がある。
その他直帰率の削減や滞在時間の増加など滞在指標の改善にも明確な良い影響を与えています。
建物詳細ページをISR化した後のシステム構成
それでは建物詳細ページISRの実現を紹介します。
ISRを実現したシステム構成図
動作説明
また、IESHILビル
の詳細ページを例とします。
IESHILビル
の詳細ページがレンダリングされてからユーザーに表示するまでの流れで説明します。
- Next.js AppはRails Appから
IESHILビル
のデータを取得し、IESHILビル
の詳細ページ(v1)をレンダリングします。 - Next.js Appは
IESHILビル
の詳細ページ(v1)のSurrogate-Control
ヘッダーにmax-age
とstale-while-revalidate
を設定します。max-age
の役割:IESHILビル
の詳細ページ(v1)が再利用できる(Cacheされる)最大時間を規定します。stale-while-revalidate
の役割:max-age
経過後、バックグラウンドでCacheが更新されるまでIESHILビル
の詳細ページ(v1)が再利用できる最大時間を規定します。
- Next.js APPは
Surrogate-Control
ヘッダーが設定されたIESHILビル
の詳細ページ(v1)をFastlyに返します。 - Fastlyは
Surrogate-Control
ヘッダーで指定した時間を基準として、IESHILビル
の詳細ページ(v1)をCacheし、ユーザーに返します。-
IESHILビル
の詳細ページ(v1)をCacheしてからmax-age
時間内の場合IESHILビル
の詳細ページ(v1)をユーザーに返します。
-
IESHILビル
の詳細ページ(v1)をCacheしてからmax-age
を経過かつ、stale-while-revalidate
時間内の場合IESHILビル
の詳細ページ(v1)をユーザーに返します。- 同時にNext.js Appへ
IESHILビル
の詳細ページ(v2)を新しくリクエストします。1~4が実行されます。
-
備考
今回ISRされたのは建物詳細ページになります。
建物詳細ページ以外のページは最初はFastly経由になっていますが、Fastly上での処理がなく、その後の処理的に既存のシステム構成に示した物と全く同じです。
最後に
今回はIESHILで一番アクセスされている建物詳細ページをISR化したことを紹介しました。
技術上のより詳細な話や、ISRにおけるA/Bテストなどについて、また別の記事で紹介する予定です。
気になる方はご期待ください!