Notion-Next.jsブログで画像が表示されない問題に対処する!

Published
2022-11-01
Tags

この記事では「Notion APIから取得した画像が表示されないことがある問題」に対処する方法をまとめています。

本サイトは公開してから1ヶ月ほど、画像が表示されにくい状態が続いていました。画像が表示されないサイトなんてそもそも見る気が起きないですよね!

この致命的とも言える問題は早急に解決する必要があると思っていましたが、中々解決策を見つけられずにいました。しかし、このたび解決に至ることができたのでその方法を紹介していきます。

取り急ぎ結論だけいうとNotionの画像には有効期限があるため、期限切れの場合画像を再取得すること」で解決することができます。

今回の問題はNotion、Next.jsでブログサイトを作っていると、必ずといって良いほど遭遇する問題になりますので、もし同じような悩みを抱えていたらぜひ参考にしてみてください。


ISRのかんたんな仕組み

今回の問題は本サイトで採用しているNext.jsのISR(= Incremented Server Rendering)という仕組みと深く関わりがあります。まずはこのISRという仕組みをかんたんに説明していきますね。

以下の図はISRのざっくりとしたイメージです。

上図の動きを具体的に説明すると次のようになります。

①, ② リクエストがあったら、キャッシュデータ(一時データ)を返す

③ Revalidate(=再検証)の時間が過ぎたら、キャッシュデータが古くなったとみなされ更新を開始

さらにキャッシュの更新後も次のリクエストではいったんは古いキャッシュを返し、その次のリクエスト時に更新したキャッシュを返します。


Revalidateはソースコード上では、下記のようにgetStaticPropsのreturnでpropsと一緒に指定します。ちなみにrevalidateを設定するとISR、設定しないとSSG(= Statc Site Generation)の動きをします。

export const getStaticProps: GetStaticProps<Props> = async () => {
  const pages = await fetchPages();
  return {
    props: {
      pages,
    },
    revalidate: 10 // ← 秒数で指定する
  };
};


ISRとNotionを組み合わせたときの問題点

Notion APIの公式HPによると、以下のようにNotionに保存されている画像ファイルには1時間の有効期限が設けられているとのこと。

データベースまたはページがクエリされるたびに、Notion がホストするすべてのファイルに対して新しいパブリック URL が生成されます。 パブリック URL は 1 時間ごとに更新され、以前のパブリック URL は 1 時間のみ有効です。 URL の有効期限が切れる正確な時刻は、ファイル オブジェクトの expiry_time プロパティで確認できます。

つまり、1時間の有効期限を過ぎるとそれを更新しない限り画像が表示できなくなってしまうということです。

先ほど説明したISRと絡めて説明すると、上図のように1時間以上経った後のリクエストではRevalidateが走りますが、先述したようにISRではいった古いデータを返す仕様なっているため、有効期限が切れたままの画像を表示しようとしてしまいます。

このようにNotionの仕様が先ほど説明したISRとの相性が悪いということがわかります。

そもそもNotion APIの公式HPでは「静的に参照すべきではない」と述べられていました。


パブリック URL は 1 時間ごとに有効期限が切れるため、静的に参照するべきではありません。パブリック URL が直接参照されている場合、有効期限が切れるとその URL でファイルにアクセスできなくなり、Notion API を介して新しい URL を取得する必要があります。


画像が表示されない問題の解決方法

以下の図のような流れで有効期限が切れていたら、Notionから新しい画像URLを取得するようにします。

上記の流れを実現するため主に以下の二つ実装を加えました。

  • useSWRの導入
  • next/imageから通常のimgタグに切り替え

今回採用した方法では新しい画像を再取得するためにuseSWRというフックスを利用しました。

useSWRはNext.jsを運営するVercelが提供しているライブラリです。


useSWRの導入

useSWRの機能をざっくり説明すると、データをなるべく最新に保つ機能です。具体的にはブラウザにマウントされてから最新のデータを読み直すということをやっています。

以下は公式ページから抜粋したコードになります。

import useSWR from 'swr'

function Profile() {
  const { data, error } = useSWR('/api/user', fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}

ここに画像の有効期限を確認し、切れていたらuseSWRが走るようなロジックを組み込むことで必要に応じたデータの更新が可能になります。

import useSWR from 'swr'

function Profile() {
  const { data, error } = useSWR(expiredImageChecker && '/api/user', fetcher)
	// expiredImageChecker: 有効期限が過ぎた画像が記事に含まれているかチェックする関数

  //略
}


next/imageから通常のimgタグに切り替え

next/imageに画像を最適化してくれる機能がありますが、Vercelの無料プランだと月1000件までの上限がありました。画像を更新するたびにURLが変わるためこの上限はかんたんに突破してしまいうことが容易に想像できます。

したがって、next/imageをやめ通常のimgタグを採用するように変更しました。


最後に

今回は「Notion APIから取得した画像が表示されないことがある問題」の解決方法を紹介しました。

実はこの方法にたどり着くまで以下の方法を検討したのですが、どうしてもNotionを使うメリットが消滅してしまったり、デメリットが大きくなってしまうためボツにしていました。

  • Notionを使わず代わりに記事はGithub上で管理する → Notionに比べ記事作成から管理までがやりづらい
  • ISRではなくSSRを採用する → 表示速度が格段に遅くなる

ともあれ、画像がちゃんと表示されるようになってホッとしています。これから本格的に記事の投稿が出来そうです。