14
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Firebase HostingでNext.jsのISRっぽいことを実現する

Posted at

はじめに

先日このような記事を書きました。

タイトルの通りISRはFIrebase Hosting (Cloud Function) では動きませんでした。
理由は以下の説明がとてもわかりやすかったです。

ISR の本体は、 SSG された HTML をキャッシュし、ユーザのアクセス応じて適切に生成・配信・破棄を行う機能です。
単に IaaS やコンテナにデプロイしただけでは、CDN の設定をすればキャッシュはされるものの、パージが ISR の想定通りには動きません。
コンピューティングリソース内部にキャッシュする(ステートフルにする)場合はその限りではありませんが、 CDN を通さない分のパフォーマンス影響が発生しますし、これは ISR とは異なるものとなります。
lambda や cloud functions ではもはやまともに動かないでしょう。

Next.js のインフラ要件と選択肢

ということでISRの実現が難しいことは理解しましたが、諦めきれなかったので、
今回はISRではなく、ISRっぽい動きの実現を目指します。

やっていく

この記事がとても参考になりました。

アプローチとしてはざっくり書くとこんな感じです。
SSG + ISR ➡︎ SSR + Cache-Control

サーバーサイドレンダリングした内容をCDNにキャッシュし、
そのキャッシュの保持期間をCache-Controlで指定します。

stale-while-revalidateに対応しているCDNであればISRっぽい挙動を実現できます。

そしてどうやらFirebase Hostingはstale-while-revalidateに対応していることがわかりました。

おそらくですが、理由はFirebaseのCDNはstale-while-revalidateに対応しているFastlyが担っているからだと思われます。

Fastly, Inc., headquartered in the United States, to deliver content and managed services on Firebase Hosting to end-users, via the Fastly content delivery network.
"米国に本社を置くFastly, Inc.が、Firebaseホスティング上のコンテンツやマネージドサービスを、Fastlyコンテンツデリバリーネットワークを介してエンドユーザーに配信する。"

Firebase Subprocessors

さっそくコードを書き換えていく

getStaticPathsは不要になるので消し、
getStaticPropsgetServerSidePropsに書き換え、revalidateも消します。

index.jsx
// export async function getStaticPaths() {
//   return {
//     paths: [],
//     fallback: true,
//   };
// }

//export async function getStaticProps({ params }) {
export async function getServerSideProps(context) {

//省略

 rreturn {
    props: {
      data,
    },
    // revalidate: 600,
  };
}

そしてCache-Controlを設定します。
参考:Cache-Control を設定する
参考:getServerSideProps (Server-side Rendering)

index.jsx

export async function getServerSideProps(context) {
  context.res.setHeader("Cache-Control", "public, s-maxage=3600, stale-while-revalidate=7200");

//省略

return {
    props: {
      data,
    },
  };
}

s-maxageの設定で3600秒 (1時間) はキャッシュを表示し、その後のアクセスでは1度キャッシュを表示した上で裏側でキャッシュの更新をし、その次のアクセスでは新しいキャッシュを表示する。
かつstale-while-revalidateの設定で7200秒経過した時点でキャッシュが破棄され、その次のアクセスでは最新のデータが表示される。

という設定をしました。
細かいCache-controlの説明はMDNのサイトがわかりやすいです。

デプロイ

これでCloud Functionsにデプロイすると、無事ISRっぽい動きになりました。

注意点:プリフェッチの挙動について

冒頭で示した記事内に記載されていますが、
getServerSidePropsに変更したことによってプリフェッチの挙動が変わります。

getStaticPropsを使ったときはページのデータまでプリフェッチが行われます。一方でgetServerSidePropsを使ったときはJSファイルのみがプリフェッチされ、データまではプリフェッチされません。
stale-while-revalidate対応のCDNでISRと同じような挙動を実現する

VercelでISRをしていたときは中のデータまでプリフェッチしてくれましたが、
今回の方法ではデータまでプリフェッチされません。

一般的なコンテンツサイトであればあまり問題にならないかもしれませんが、
設計によっては注意が必要です。

例えばちょっと極端な例ですが、
ナビバーに4つの目次があり、クライアントサイドナビゲーションでページ遷移するとします。
A, B, C それぞれ DB からデータをフェッチしており、
"すべての情報" ページは A, B, C すべてのデータをフェッチしています。
 

  クライアントサイドナビゲーションとはいえ、ページが分かれているのでそれぞれのページがCDNにキャッシュされることになりますが、 問題は**キャッシュの破棄されるタイミングがバラつくために、ページ間の情報に不整合が出る**ことです。

例えば、 C の DB に情報が追加されました。
その後キャッシュが破棄されたタイミングで "Cの情報" ページが更新されました。
しかし、"すべての情報" ページが古いキャッシュのままだったとします。

するとこのような動きになります。

  今回の例はページの中にナビバーも含めていた極端な例でしたが、この例でもVercelのISRでは中のデータまでプリフェッチすることにより問題なく機能するため、 Vercelから別の場所に移動するときは、設計によってはこのプリフェッチの挙動に違いによって影響がありますよというお話でした。

さいごに

Vercelに上げる気満々で作ったNext.js製アプリが大人の事情でFirebaseを指定されることになり、戸惑いながら色々調べていましたが、
Next.js のインフラ要件と選択肢
stale-while-revalidate対応のCDNでISRと同じような挙動を実現する
これらの記事のおかげで何とか解決することができました。
(本当はもっと他にもここには挙げきれないぐらいたくさんの記事を参考にしました)

書いてくださった皆さんには本当に感謝しています。ありがとうございました。

14
11
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?