🐱
[Next.js + App Router]DBの値から動的にmetadataを設定する
前書き
-
App Router
で、DBの値からmetadataを設定する書き方について、有用な記事を見つけられなかったので、自分なりのベストプラクティスを記します。
結論
- ORMに
prisma
を使用しています
src/app/todo/[id]/page.tsx
import { Todo } from '@/components/pages/Todo';
import { prisma } from '@/lib/prisma';
import { Metadata } from 'next';
import { cache } from 'react';
type Props = {
params: {
id: string;
};
};
const getTodo = cache(async (id: number) => {
const todo = await prisma.todo.findUniqueOrThrow({ where: { id } });
return todo;
});
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const todo = await getTodo(Number(params.id));
return { title: todo.name };
}
const TodoPage = async ({ params }: Props) => {
const todo = await getTodo(Number(params.id));
return <Todo todo={todo} />;
};
export default TodoPage;
Point
-
cache
関数を使用する - https://nextjs.org/docs/app/building-your-application/data-fetching/caching#react-cache
解説
Pages Router(従来)では
以下のように直感的に書ける。
import Head from 'next/head'
const TodoPage = async ({ params }: Props) => {
const todo = await getTodo(Number(params.id));
return (
<>
<Head>
<title>{todo.name}</title>
</Head>
<Todo todo={todo} />
</>;
);
};
export default TodoPage;
App Routerでは
-
next/head
が機能しない - metadataを設定するのに使えるのは、以下のみのよう from 公式Docs
- 静的なら
metadata
- 動的なら
generateMetadata
- 静的なら
generateMetadata
を使用して、シンプルに書くと以下のようになる。
const getTodo = async (id: number) => {
const todo = await prisma.todo.findUniqueOrThrow({ where: { id } });
return todo;
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const todo = await getTodo(Number(params.id));
return { title: todo.name };
}
const TodoPage = async ({ params }: Props) => {
const todo = await getTodo(Number(params.id));
return <Todo todo={todo} />;
};
但し、この書き方だと、クエリを2回発行してしまいよろしくない。
実際に発行されたクエリ↓
- wait compiling /todo/[id]/page (client and server)...
- event compiled client and server successfully in 1166 ms (432 modules)
- wait compiling...
- event compiled successfully in 91 ms (211 modules)
prisma:info Starting a postgresql pool with 17 connections.
# 1回目
prisma:query SELECT "public"."Todo"."id", "public"."Todo"."name", "public"."Todo"."created_at", "public"."Todo"."updated_at" FROM "public"."Todo" WHERE ("public"."Todo"."id" = $1 AND 1=1) LIMIT $2 OFFSET $3
# 2回目
prisma:query SELECT "public"."Todo"."id", "public"."Todo"."name", "public"."Todo"."created_at", "public"."Todo"."updated_at" FROM "public"."Todo" WHERE ("public"."Todo"."id" = $1 AND 1=1) LIMIT $2 OFFSET $3
- wait compiling /favicon.ico/route (client and server)...
cache
関数を使う
- reactの標準モジュールとして提供されている
- https://nextjs.org/docs/app/building-your-application/data-fetching/caching#react-cache
- 詳しく解説されている記事を見つけたのでshare
// new!!
const getTodo = cache(async (id: number) => {
const todo = await prisma.todo.findUniqueOrThrow({ where: { id } });
return todo;
});
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const todo = await getTodo(Number(params.id));
return { title: todo.name };
}
const TodoPage = async ({ params }: Props) => {
const todo = await getTodo(Number(params.id));
return <Todo todo={todo} />;
};
実際に発行されたクエリ↓
- wait compiling /todo/[id]/page (client and server)...
- event compiled client and server successfully in 1127 ms (432 modules)
- wait compiling...
- event compiled successfully in 87 ms (211 modules)
prisma:info Starting a postgresql pool with 17 connections.
// 1回のみ👏
prisma:query SELECT "public"."Todo"."id", "public"."Todo"."name", "public"."Todo"."created_at", "public"."Todo"."updated_at" FROM "public"."Todo" WHERE ("public"."Todo"."id" = $1 AND 1=1) LIMIT $2 OFFSET $3
- wait compiling /favicon.ico/route (client and server)...
後書き
- DBの値からtitleを生成したいケースは割とあるかなと思い、記事にしてみました。
- ご指摘、ご提案などございましたらお気軽にコメントお願いします🙏
Discussion