Next.js App Router の not-found.tsx が呼ばれない
App Router の not-found.tsx は notFound() が throw されるか、ルートに該当パスが存在しない時のみレンダリングされる。
動的セグメントで notFound() を明示的に呼ぶか、dynamicParams = false を付ける必要がある。
#nextjs#app-router#not-found#notfound#404
公開:
要約
App Router の 404 は「ルートに対応するファイルが無い」場合と、「ページ内から notFound() が throw された」場合の 2 系統で発火する。
動的セグメントでデータが無い時に勝手に 404 にはならないので、ページ側で notFound() を明示的に呼ぶか、dynamicParams = false で静的生成範囲外を 404 にする必要がある。
よくある原因
app/blog/[slug]/page.tsxでデータ取得失敗時にreturn <div>Not Found</div>を描画している(HTTP 200 で返る)not-found.tsxがルート(app/not-found.tsx)にしか無く、ネストの 404 が反応していないように見えるmiddleware.tsで全リクエストを書き換えており、Next.js のルーティングまで届かないgenerateStaticParamsで全パターンを返しているのにdynamicParamsを指定しておらず、未知のスラッグが動的描画になっている- Pages Router の
pages/404.tsxの感覚で App Router を使っており、ファイル名を404.tsxにしている
解決策
1. notFound() を明示的に呼ぶ
import { notFound } from 'next/navigation'
export default async function Page({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
if (!post) notFound()
return <article>{post.title}</article>
}notFound() は throw する関数なので、呼んだ後の処理は実行されない。
最も近い not-found.tsx に到達するまで境界を遡る(公式リファレンス)。
2. セグメントごとに not-found.tsx を置く
app/
not-found.tsx # ルート全体の 404
blog/
not-found.tsx # /blog 配下の 404
[slug]/
page.tsx階層を遡って最も近い not-found.tsx が使われる。
Pages Router の 404.tsx は App Router では使わない(file-conventions: not-found.js)。
3. 静的生成範囲外を 404 にする
export const dynamicParams = false
export async function generateStaticParams() {
const posts = await getAllPosts()
return posts.map((p) => ({ slug: p.slug }))
}dynamicParams = false を付けると、generateStaticParams が返さなかったスラッグへのアクセスは自動的に 404 になる。
4. middleware を疑う
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}middleware の matcher が広すぎて Next.js のルーティングが阻害されると、404 そのものが発火しない。matcher で静的アセットや API を除外する。