Next.js の generateMetadata
内で notFound
を実行するとルートの not-found.tsx
を参照する
掲題のとおりなのですが、ドキュメントについても記載されていなかったためメモです。
バージョンについて
Next.js 13.4.1 で確認しています。
$ pnpm why next
Legend: production dependency, optional only, dev only
...
dependencies:
next 13.4.1
generateMetadata
とは
<meta>
タグの記述を動的に(あるいは非同期処理を用いながら)設定するための関数です。Next.js の App Router で機能します。
わかりやすい使用例としては、CMSなどから投稿データを取得してきて、それを <title>
タグに挿入するときなどです。
type Props = {
params: {
id: string
}
}
export async function generateMetadata({
params: { id },
}: Props): Promise<Metadata> {
const post = await fetchPost(id)
return {
title: post.title,
description: post.description,
}
}
export default async function PostDetailPage({ params: { id } }: Props) {
// ...
}
詳しくは公式ドキュメントへ↓
notFound
とは
こちらも Next.js の App Router で追加されたもので、いわゆる 404 Not Found ページを呼び出すための関数です。
使い方としては単純に実行するだけです。
type Props = {
params: {
id: string
}
}
export default async function PostDetailPage({ params: { id } }: Props) {
const post = await fetchPost(id)
if (!post) {
// return は不要。返し値は never 型となっている。
notFound()
}
return <div>{post.content}</div>
}
詳しくは公式ドキュメントへ↓
なにがおこったか
このサイトを App Router に移行するときに not-found.tsx
をつくってみたときに、以下のような構造で全体の Not Found ページと投稿の Not Found ページを別々のファイルとしてつくっていました。
app/
not-found.tsx // ページ全体でパスの不一致で出す用
layout.tsx
page.tsx
post/
[uid]/
not-found.tsx // 投稿の uid がなかったときに表示する用
page.tsx
app/not-found.tsx
は↓
const NotFound = () => {
return (
<html>
<head>
<title>Page Not Found.</title>
<meta name="description" content="Page Not Found." />
</head>
<body>
<h1>Page Not Found.</h1>
<a href="/">Home</a>
</body>
</html>
)
}
export default NotFound
app/post/[uid]/not-found.tsx
は↓
const UidNotFound = () => {
return (
<html>
<head>
<title>Uid Not Found.</title>
<meta name="description" content="Uid Not Found." />
</head>
<body>
<h1>Uid Not Found.</h1>
<a href="/">Home</a>
</body>
</html>
)
}
export default UidNotFound
この状態で、 app/post/[uid]/page.tsx
で notFound
へ飛ばす処理を書いてみました。
type Props = {
params: {
id: string
}
}
export async function generateMetadata({ params: { id } }: Props) {
const postMetadata = await fetchPostMetadata(id).catch((err) => {
console.error(err)
// 1. 投稿メタデータが取得できない = 存在しないとみなして 404 ページへ
notFound()
})
if (!postMetadata) {
// 2. 投稿メタデータがないため 404 ページへ
notFound()
}
const metadata: Metadata = {
title: postMetadata.title,
description: postMetadata.description,
}
return metadata
}
export default async function PostDetailPage({ params: { id } }: Props) {
const post = await fetchPost(id).catch((err) => {
console.error(err)
// 3. 投稿データが取得できない = 存在しないとみなして 404 ページへ
notFound()
})
if (!post) {
// 4. 投稿データがないため 404 ページへ
notFound()
}
return <div>{post.content}</div>
}
この状態で、 generateMetadata
内の notFound
(1と2)が実行されたときには、 「Page Not Found.」が表示されます。
つまり、 app/not-found.tsx
がレンダリングされました。
回避策
app/post/[uid]/not-found.tsx
を表示するために、今回は notFound
を generateMetadata
で実行しないように変更しました。
投稿データがないケースと投稿メタデータがないケースはほとんど一致するので、今回はこれで回避するという形をとりました。
fetchPostMetadata
の catch
は、エラーログの出力のみに変更しました。
後日談
Next.js リポジトリ上でも notFound の issue は割と上がっていたので、他にも色々あるかもしれないです。