App Routerでfetchを使えない場合のISRについて
はじめに
App RouterでISRができるfetch関数が用意されていますが、HTTPベースのリクエストにしか対応していません。firestoreやsupabaseからデータを取得したい場合にどうすべきかという記事が見当たらなかったので、投稿しました。
もっといい方法を知っている方がいれば、教えていただきたいです。(こっちが本題)
export default async function Page() {
const res = await fetch('https://api.example.com/...', { next: { revalidate: 3600 } })
const data = res.json()
return <Home data={data} />
}
結論
以下のようにすると、ISRになります。
60秒間はキャッシュされたデータが渡されるため、その間はデータベースに対してクエリが行われることはありません。
import { getData } from './utils'
export const revalidate = 60
export default async function Page() {
const data = await getData('id')
return <Home data={data} />
}
import { cache } from 'react'
export const getData = cache(async (id: string) => {
const data = await db.query('...')
return data
})
以下のページに記載がありました。
If the segment is static (default), the output of the request will be cached and revalidated as part of the route segment.
セグメントが静的 (デフォルト) の場合、リクエストの出力はルート セグメントの一部としてキャッシュされ、再検証されます。
キャッシュするページの容量が大きそうな場合は、cacheMaxMemorySize
を設定しておきましょう。
デフォルトは50M
です。
/** @type {import('next').NextConfig} */
const nextConfig = {
cacheMaxMemorySize: 50 * 1024 * 1024 // 50M
}
module.exports = nextConfig
cache
公式ページの以下のページにreactのcacheを使うと、fetchを利用できないサードパーティーライブラリーからのデータ取得ができると記載があります。
cacheを使わないと、revalidateで指定した時間がたっていなくてもデータの再検証がされている場合があり、使った方が安定するので利用しています。
page.tsx内に記載するrevalidateはpageの生成間隔というよりは、ページ生成するためのデータをキャッシュする間隔なのかなと推測しています。
cacheを使うと以下のようにlayout.tsxとpage.tsxで同じデータを取得するときもキャッシュされるので、dbへのクエリは1回で済みます。
ただ、Dynamic Routesを使用すると、revalidateで指定した値が無視されてページアクセスのたびにクエリが走ります。
import { getData } from './utils'
export const revalidate = 60
export default async function Layout({
params: { id },
}: {
params: { id: string }
}) {
const item = await getData(id)
// ...
}
import { getData } from './utils'
export const revalidate = 60
export default async function Page({
params: { id },
}: {
params: { id: string }
}) {
const data = await getData(id)
return <ItemPage data={data} />
}
unstable_cache
これが今回求めていた機能ですが、公式の記載でもある通り、まだ開発中のようです。
使い方は以下のような感じかなと思います。
実際使ってみたところ、ローカル上でデバッグした時はちゃんと動いていそでしたが、Vercelにデプロイすると、ビルド時以外は実行されず、ただのSSRになりました。
import { unstable_cache } from 'next/cache'
export default async function Page() {
const data = await unstable_cache(
async () => {
const data = await db.query('...')
return data
},
['cache-key'],
{
tags: ['a', 'b', 'c'],
revalidate: 10,
}
)()
return <Home data={data} />
}
いろいろ試したときのサイトです。
あとがき
このあたりがまとまった記事がなく、自分はこの部分でだいぶ時間を使ってしまったので、誰かの一助になればと思います。
補足
ISRをするときはnext/linkのprefetchにfalseを設定したほうがよさそうです。
指定していないと、ページが表示された際、Linkで指定されているページがrevalidateで指定した期間は関係なく再検証されます。
以下の例だと、Homeのページが表示された際、pageA、pageBも再検証されます。
import Link from "next/link";
export default function Home() {
return (
<main>
<h1>Home</h1>
<Link href='/pageA'>
pageA
</Link>
<br />
<Link href='/pageB'>
pageB
</Link>
</main>
)
}
以下のように、prefetch={false}を指定すると、リンク先のページは再検証されません。
import Link from "next/link";
export default function Home() {
return (
<main>
<h1>Home</h1>
<Link href='/pageA' prefetch={false}>
pageA
</Link>
<br />
<Link href='/pageB' prefetch={false}>
pageB
</Link>
</main>
)
}
Discussion