Next.jsのISRをGAE + CDNで試す実験
 catnose
catnoseNext.jsのISRをVercel以外でやろうとするとなかなかしんどそう。
そこで、Next.jsにデフォルトで用意されているISR(getStaticPropsによるもの)は使わずに、ISRと同じ挙動を「SSR + CDN」で実現できないか試してみる会。
 catnose
catnose今回の実験では、Next.jsを動かすサーバーとしてGoogle App Engine(GAE)を使ってみる。
 catnose
catnose失敗:Next.jsのISRをとりあえずGAEで動かしてみる
export default function Page(props) {
  return (<p>現在時刻は{props.currentTime}です。</p>);
}
export async function getStaticProps() {
  const date = new Date();
  const currentTime = date.toLocaleString();
  return {
    props: { currentTime },
    revalidate: 10,
  };
}
export const getStaticPaths = async () => {
  return {
    paths: [],
    fallback: "blocking",
  };
};
結果
GAEのログを見ると以下のようなエラーが発生している。
A 2021-03-15T09:34:03.960288Z Failed to update prerender files for /isr [Error: EROFS: read-only file system, open '/workspace/.next/server/pages/isr.html'] { 
A 2021-03-15T09:34:03.960312Z   errno: -30, 
A 2021-03-15T09:34:03.960319Z   code: 'EROFS', 
A 2021-03-15T09:34:03.960325Z   syscall: 'open', 
A 2021-03-15T09:34:03.960331Z   path: '/workspace/.next/server/pages/isr.html' 
A 2021-03-15T09:34:03.960336Z } 
Next.jsのISRはファイルシステムに書き込む仕組みになっているからか(まだ実装部分を読んでおらずよく分かっていない)エラーが出る模様。
仮に書き込みができたとしても複数のインスタンス間でキャッシュが共有するのが難しそう。
 catnose
catnose
 GAE + stale-while-revalidateに対応したCDNを試す
ここからが本題。Next.jsのISRを使わずにISRと同じ挙動(stale-while-revalidate)を実現できないか試みる。stale-while-revalidateについては以下の記事が分かりやすい。
具体的には以下のことを試す。
- Next.jsではgetServerSidePropsを使ってSSRをする。このときCache-Controlヘッダにstale-while-revalidateを含めるようにする。
- Next.jsの前にstale-while-revalidateヘッダに対応したCDNを配置する
これでNext.jsのISRと同じ挙動になるかを調べる。Next.jsは単純にSSRをするだけなので、他のフレームワークを使っても同じことができるはず。
CDNサービス
2021年2月時点でstale-while-revalidateに対応したCDNサービスは例えば以下。
- Fastly
- CloudCDN
- Cloudflare
ただ、Cloudflareは料金表を見ると「キャッシュ最小TTL有効期限」が$200 / 月のプランでも30秒以上になってるのが気になる。この制限がmax-ageの最小時間に該当する場合、ISR的なことをやろうとしても30秒以上は再検証されないことになる
 catnose
catnoseGAE + CloudCDNで試してみた
getServerSidePropsの中でレスポンスヘッダのCache-Controlに以下の値をセットする
public, max-age=10, stale-while-revalidate=86400"
これで10秒間はキャッシュ。それ以降はデータの再取得が行われるまでは一旦古いキャッシュを返す。1日(86400秒)以上経つと古いキャッシュも削除される。
デプロイして試してみたところ成功した。以下のようにISRっぽい動きになった。
- 初回リクエストでは最新のデータが表示
- 10秒間は何度リロードしても初回のキャッシュが表示される
- 5分後にアクセスしても初回キャッシュが表示される。リロードすると最新のデータに書き換わる
 catnose
catnoseFastlyを使ってもいけるらしい
 catnose
catnoseGAE + CDNにすると[GAEのエッジキャッシュ]と[CDNのキャッシュ]の両方が効いてしまう(?)
実際にGAEでNext.jsを動かしてみて思い出したのだが、GAEにはデフォルトでエッジキャッシュが備わっている。Cache-Controlヘッダが指定されていると、GAEの方でもレスポンスがキャッシュされることになる(?)
追記:CloudCDNと連携した場合GAEデフォルトのエッジキャッシュは効かない模様 🙆♀️
検証してみたところCloudCDNをGAEにつなぐとGAEインスタンス ⇔ CloudCDNとなる。
ただし、CloudflareなどのCDNサービスを使う場合はどうなのか不明
 catnose
catnose最終的なまとめ



