【Next.js + Supabase】動的にmetaを出力する際に2回データ取得してしまう問題を解決する
Next.jsで動的にメタデータを出力する際には下記のような記述を行います。
// 動的にメタデータを出力する
export async function generateMetadata({ params }: { params: { id: string } }) {
const data = await getPostData(params.id);
return {
title: data.title,
description: data.description
};
}
// ブラウザ画面に出力する
export default async function MyPage({ params }: { params: { id: string } }) {
const data = await getPostData(params.id);
return (
<>
<p>{data.title}</p>
</>
);
}
この記述には問題があります。
【問題】データ取得が2回行われてしまっている
const data = await getPostData(id);
でデータ取得を行っているのですが、 generateMetadata
内で1回、 MyPage
で1回、合計2回も行われています。
どちらも同じデータを取得しているのに、謎に2回もリクエストしている影響でサーバーサイドに余分な負荷がかかってしまいます。メタデータのためだけに余分なリクエストを投げるのは勿体ないので、1回だけで済ませる方法をご紹介します!
※この記事は下記を参考にさせて頂きました。(参考というか丸パクですね...w)
【解決】キャッシュを使おう
下記のように記述をするとキャッシュが効き、1回のリクエストに抑えることができます。
import { cache } from "react";
import { notFound } from "next/navigation";
import { useSupabase } from "@/hooks/useSupabase";
import type { Database } from "@/types/supabase-schema";
export const dynamic = "force-dynamic"; // SSR
// GetMyPost関数をキャッシュが効くようにする
const getPostInfo = cache(async (id: string) => {
const postInfo = await GetMyPost(id);
return postInfo;
});
export async function generateMetadata({ params }: { params: { id: string } }) {
const postInfo = await getPostInfo(params.id);
return {
title: postInfo[0].title,
description: postInfo[0].description
};
}
export default async function PostPage({ params }: { params: { id: string } }) {
const postInfo = await getPostInfo(params.id);
return (
<>
<h1>{postInfo[0].title}</h1>
<p>{postInfo[0].contents}</p>
</>
);
}
/** ================================================ **/
// 下記でSupabaseからデータ取得をしている。
// 個人的な考えで .select() を使わずに `.rpc()` を使っていますが、selectとほぼ同じだとお考えください。
/** ================================================ **/
type GetMyPostType = Database["public"]["Functions"]["get_my_post_by_id"]["Returns"];
async function GetMyPost(id: string): Promise<GetMyPostType> {
const { supabase } = useSupabase();
const { data, error } = await supabase.rpc(`get_my_post_by_id`, {
p_id: id,
});
if (error || data.length === 0) {
notFound();
}
return data;
}
cache
を使おう
【ポイント1】const getPostInfo = cache(async (id: string) => {
const postInfo = await GetMyPost(id);
return postInfo;
});
React標準のモジュールの cache で囲むだけでリクエスト単位でキャッシュ化できます。とてもシンプルにキャッシュ化できますね!
【ポイント2】データ取得はcache関数を定義したものを使う
const postInfo = await getPostInfo(params.id);
この記述が2回出てきておりますが、これは「ポイント1」で挙げたキャッシュ関数を利用しているため、何度呼び出されてもキャッシュからデータを提供してくれるようになっています。
間違っても GetMyPost()
をそのまま叩かないように注意してください。2回リクエストが行われてしまいます。
【付録】本当に2回リクエストされてないか確認したい
async function GetMyPost(id: string): Promise<GetMyPostType> {
const { supabase } = useSupabase();
const { data, error } = await supabase.rpc(`get_my_post_by_id`, {
p_id: id,
});
if (error || data.length === 0) {
notFound();
}
console.log("リクエストされたよ!");
return data;
}
上記のように console.log()
を追加し、ターミナルで確認してみてください。「リクエストされたよ!」が一度しか表示されないはずです。ということは、問題なくキャッシュ関数が効いているということが確認できます。
さいごに
Supabaseはとても簡単に利用できますが、こういう細かいところが戸惑うポイントですね。皆さんも細かいところをどんどん記事にしていき日本のSupabaseユーザーコミュニティを盛り上げていきましょう!
ノウハウ積みまくろ!
Discussion