SSG と ISR
今日のタブ記事
Next.js における SSG(静的サイト生成)と ISR について(自分の)限界まで丁寧に説明する
このブログがまさに Next.js の SSG を採用しているんだけど、たしか最初の投稿で「ISR についてあんまりちゃんと理解してない」みたいなことを書いた。
ので、この記事を読んで SSG と ISR とその他(SSR や CSR)について完全に理解しようと思う。
以下この記事をかなり雑に自分的にまとめたもの ↓
CSR と SSR
- CSR = Client Side Rendering
- そもそも JS は HTML の script タグの内部で動作する
- JS で Web アプリケーションを作成する場合、基本的にはクライアント側でレンダリングされるのが基本
- クライアント(=ブラウザ)で JS のレンダリング処理
- React とか Vue とかの基本はこれ
- アプリケーションが大きくなるほどクライアントで処理する JS が増えてレンダリングにかかる時間が増える
- SSR = Server Side Rendering
- サーバー側でロジックとレンダリングの処理を完結させて HTML を生成しそれをクライアントに返す
- CSR と違いクライアント側で JS の処理をする必要がないので、クライアントの JS が増えてしまう問題が解決する
- ※ SSR と CSR はレンダリングの場所が違うだけで クライアントの JS が増減するというのは違うのでは、という指摘をもらいました。
- ユーザーからのリクエストを受けてからサーバー側で処理をして HTML を生成するので、オーバーヘッドはどのみち発生する
ここまではなんとなく把握している
SSG
- ユーザーからのリクエストで処理を始めて HTML を生成するのではなく、もうあらかじめ HTML をレンダリングした状態にしておいてリクエストが来たらそれを返すだけ
- あらかじめレンダリングしてあるので生成済みの HTML を返すだけで CSR や SSR よりも高速
- アプリケーションのビルド時にレンダリングしてしまい全てのページの HTML を生成しておき、ユーザーからのリクエストにはその HTML を返す
- 真の力を引き出すためには、HTML をアプリケーションサーバーから都度返すのではなく、CDN にキャッシュを置いておき、リクエストしたユーザーに近い CDN からキャッシュを返すというのがよい
- Vercel は Vercel Edge Network という CDN をデフォルトで備えている
- Vercel にアプリケーションをデプロイすると、世界各地にある Vercel の CDN にコンテンツが自動的にキャッシュされる。すごい
データフェッチについて
- SSG = 静的な HTML を事前に作成することであれば、それは最初から自分で HTML を書いて配信するのと同じ?
- データを取得する部分を HTML の script タグに手書きで書くことになるし、その処理はブラウザ上で実行されるので結局 CSR とあまり変わらない
- そもそもすべての HTML にデータの取得処理を書くのは現実的じゃない
- SSG はアプリケーションのビルド時にデータを取得する
- 手書きの HTML はデータ取得した状態のものを書くことはできないので SSG とは根本的に違う
getStaticProps
やgetStaticPaths
のところはだいたい把握している、と思う
フォールバック
- SSG はアプリケーションをビルドしたタイミングでのみデータフェッチとレンダリングを行うので、自分以外のユーザーがページを追加したりデータを追加したりした場合は対応できない
- ユーザーがページやデータを追加したタイミングで都度ビルドしていたら無限に時間が足りないし非効率
- アプリケーションのビルドから次のアプリケーションのビルドまでの間に内容が変化しうるデータを利用する場合の SSG はどうすればよいのか
fallback: true
getStaticPaths
でfallback: true
を返すgetStaticPaths
はビルドして生成する HTML に対するすべてのパスを返すが、fallback: false
だと存在していないパスにアクセスしたときに 404 を返すfallback: true
だと 404 を返さず、データを取得する必要がある部分以外がレンダリングされた HTML を返す => 静的なページにフォールバックする- そもそもフォールバックとは、
フォールバックとは、通常使用する方式や系統が正常に機能しなくなったときに、機能や性能を制限したり別の方式や系統に切り替えるなどして、限定的ながら使用可能な状態を維持すること。また、そのような切り替え手順・動作のこと。
paths
で指定されたパス以外のパスにリクエストがあったときに、データフェッチをせずに生成できる部分のみ返して限定的な使用可能状態を維持するという感じ- React の error boundary という概念にもフォールバックが出てくる
- 子コンポーネントツリーで発生したエラーをキャッチしてエラーを記録して、クラッシュしたコンポーネントツリーの代わりにフォールバック用の UI を表示する
- その後、そのパスのページ表示に対応するデータがあれば、クライアントで JS を実行してデータ取得が走るとともに、サーバーでも同様の対応するデータの取得とレンダリングが行われる
- その挙動以降に同じパスにリクエストがあった場合は、生成された HTML のキャッシュが返るようになる
- つまり 2 回目以降は普通に SSG で指定されたパスと同様の挙動になる
- ビルド時に指定した paths 以外にリクエストしても、それに対応するデータがあれば SSG したページと同様に正しく表示することができる
fallback: 'blocking'
fallback: true
だと、paths で指定されていないページをリクエストしたときに不完全な HTML が一時的に表示されてしまう- フォールバックを blocking するということは、一時的に不完全な HTML を表示することをブロックするということ
- つまり、不完全な HTML は表示させず、paths で指定されていないページをリクエストしたときにサーバーサイドでデータフェッチとレンダリングが行われて、それが完了したら返却される
- その後のアクセスは
fallback: true
の時と同様にキャッシュされる
ISR
fallback: true
やfallback: 'blocking'
を使っても、アプリケーションのビルド時に生成されたキャッシュやフォールバックされてから生成されたキャッシュを使うので、そのキャッシュに対応するデータが更新された場合はその更新が効かず、次のビルドまで更新されたデータが表示できない- SSG は動的なデータを扱うことはできない?
- => ISR
- SSG の挙動 + 一定時間ごとにバックグラウンドでデータの再取得と再レンダリングを行う
- 一定時間の指定は、
getStaticProps
でrevalidate
を返すようにするだけ revalidate
で指定した秒数までは SSG と同様にキャッシュを返すが、指定した秒数後に初めてリクエストがきた場合、キャッシュを返しつつバックグラウンドでデータの再取得と再レンダリングを行い、その次のリクエストからは再生成したページのキャッシュを返す- これを指定した秒数ごとに繰り返す
- アプリケーションをビルドし直すことなく更新されたデータを反映させることができる
SSG とそれ以外
- ISR は便利だが、
revalidate
で指定した時間が経過して以降の最初のリクエストがトリガーとなって再生成を行うため、そのリクエストは更新前の情報になってしまう - それを許容するかどうかを正しく判断する
- 絶対に常に最新の情報が表示されていてほしい場合は ISR は使用できないが、そうでない場合は ISR を使うのが非常に便利ということになる
まとめ
- SSR と CSR => 常に最新の情報を表示できるがオーバーヘッドが大きい
- SSG の
fallback: false
=> ビルド時のデータに対応する情報のみ表示でき、それ以外は 404 - SSG の
fallback: true
とfallback: 'blocking'
=> ビルド時のデータに対応する情報の表示に加え、新しく生成されたデータに対応する情報も多少のオーバーヘッドがありつつ表示可能。ただし既存のデータが更新された場合は古い情報になってしまう - ISR => ビルド後に新しく生成されたデータに加え、既存のデータが更新された場合でも表示可能。ただしトリガーとなるリクエストでは古い情報が表示されてしまう
今日はたくさん読んで書いたのでこのへんで