🍪
Next.js(AppRouter)でDynamicFunctionを使ってもCacheHitさせる
はじめに
Next.js(AppRouter)をみなさん上手く扱えていますでしょうか?
弊社では現在新規プロダクトで使っているのですが、パフォーマンスが良くないという問題にぶちあたっていました。
今回はその解決方法を一部共有したいと思います。
前提条件として弊社は以下の環境となっています。
next : 14.1.0
react : 18.2.0
react-dom : 18.2.0
typescript : 5.3.3
Node.js : 18.17.1
デプロイ環境 : Vercel
問題
Next.js(AppRouter)では、headers
やcookies
といったDynamicFunction
が提供されています。
Using any of these functions will opt the whole route into dynamic rendering at request time.
ドキュメントにも書いてあるのですが、DynamicFunction
を利用すると動的レンダリングになってしまいます。
せっかくgenerateStaticParams
で静的なページを作成していても、そのページでheaders
やcookies
が使われていると毎回サーバーサイドレンダリング(以下SSR)が行われてしまいページのレスポンスが非常に遅い状態(2~3s)でした。
解決方法
方針としては以下になります。
-
generateStaticParams
で生成された静的ページを返却するためにcookies
の呼び出しをコンポーネントから取り除く -
cookies
を使う処理をServerActions
に移行する -
cookies
によって動的に表示を変えたい部分はClientComponent
にて適宜変更する
generateStaticParams
で生成された静的ページを返却するためにcookies
の呼び出しをコンポーネントから取り除く
page.tsx
やServerSideComponent
等で以下のようにcookies
を呼び出すと思いますが、
const cookieStore = cookies()
const cookieHoge = cookieStore.get("hoge")
const cookieFuga = cookieStore.get("fuga")
これらを一旦削除します。
cookies
を使う処理をServerActions
に移行する
useEffect
にてServerActions
を呼び出し再度cookies
を使った情報で表示し直すようにします。
"use server"
export async function handleChangeList() {
const cookieStore = cookies()
const hoge = cookieStore.get("hoge")
const fuga = cookieStore.get("fuga")
if (hoge === null || fuga === null) {
return { null, "", "" }
}
const { hogeFugaList } = await fetchHogeFuga({
hoge,
fuga
})
return { hogeFugaList, hoge, fuga }
}
export async function saveHogeToCookies(value: string) {
cookies().set("hoge", value)
}
export async function saveFugaToCookies(value: string) {
cookies().set("fuga", value)
}
cookies
によって動的に表示を変えたい部分はClientComponent
にて適宜変更する
"use client"
import { useEffect, useState } from "react"
type Props = {
hogeFugaList: HogeFuga[]
}
export default function HogeFugaList({ hogeFugaList }: Props) {
const { stateHogeFugaList, setHogeFugaList } = useState(hogeFugaList)
useEffect(function () {
void handleChangeList()
.then(({ hogeFugaList, hoge, fuga }) => {
if (hogeFugaList === null) {
return
}
setHogeFugaList(hogeFugaList)
})
.catch((e) => console.error(e))
}, [setHogeFugaList])
return (
<ul>
{stateHogeFugaList.map((hogeFuga, index) => {
return <li key={index}>{hogeFuga}</li>
})}
</ul>
)
}
おわりに
変更前
変更後
上記の解決策を行うことでCacheHitするようになり、レスポンスタイムも速くなりました!
CacheHitさせるための1案ですので、要件によって適宜応用していただければと思います。
参考
Discussion
動的部分はSuspenseすればいいのでは
Suspenceもありますね!
自分のプロダクトを想像しながら書いていたので、ClientComponentではない場合はuseEffect使うよりシンプルですね!
ありがとうございます!