Next.jsのISRをGAE + CDNで試す実験
Next.jsのISRをVercel以外でやろうとするとなかなかしんどそう。
そこで、Next.jsにデフォルトで用意されているISR(getStaticProps
によるもの)は使わずに、ISRと同じ挙動を「SSR + CDN」で実現できないか試してみる会。
今回の実験では、Next.jsを動かすサーバーとしてGoogle App Engine(GAE)を使ってみる。
失敗: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はファイルシステムに書き込む仕組みになっているからか(まだ実装部分を読んでおらずよく分かっていない)エラーが出る模様。
仮に書き込みができたとしても複数のインスタンス間でキャッシュが共有するのが難しそう。
stale-while-revalidate
に対応したCDNを試す
GAE + ここからが本題。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秒以上は再検証されないことになる
GAE + CloudCDNで試してみた
getServerSideProps
の中でレスポンスヘッダのCache-Control
に以下の値をセットする
public, max-age=10, stale-while-revalidate=86400"
これで10秒間はキャッシュ。それ以降はデータの再取得が行われるまでは一旦古いキャッシュを返す。1日(86400秒)以上経つと古いキャッシュも削除される。
デプロイして試してみたところ成功した。以下のようにISRっぽい動きになった。
- 初回リクエストでは最新のデータが表示
- 10秒間は何度リロードしても初回のキャッシュが表示される
- 5分後にアクセスしても初回キャッシュが表示される。リロードすると最新のデータに書き換わる
Fastlyを使ってもいけるらしい
GAE + CDNにすると[GAEのエッジキャッシュ]と[CDNのキャッシュ]の両方が効いてしまう(?)
実際にGAEでNext.jsを動かしてみて思い出したのだが、GAEにはデフォルトでエッジキャッシュが備わっている。Cache-Control
ヘッダが指定されていると、GAEの方でもレスポンスがキャッシュされることになる(?)
追記:CloudCDNと連携した場合GAEデフォルトのエッジキャッシュは効かない模様 🙆♀️
検証してみたところCloudCDNをGAEにつなぐとGAEインスタンス ⇔ CloudCDN
となる。
ただし、CloudflareなどのCDNサービスを使う場合はどうなのか不明
最終的なまとめ