できない.dev

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 にする必要がある。

よくある原因

  1. app/blog/[slug]/page.tsx でデータ取得失敗時に return <div>Not Found</div> を描画している(HTTP 200 で返る)
  2. not-found.tsx がルート(app/not-found.tsx)にしか無く、ネストの 404 が反応していないように見える
  3. middleware.ts で全リクエストを書き換えており、Next.js のルーティングまで届かない
  4. generateStaticParams で全パターンを返しているのに dynamicParams を指定しておらず、未知のスラッグが動的描画になっている
  5. 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 を除外する。

この記事は役立ちましたか?