レビュー済み·難易度: 初級·更新: 2026-04-18

Next.js で not-found.tsx を使ってカスタム 404 ページを作る

notFound() 関数と not-found.tsx を組み合わせてカスタム 404 ページを実装する例。動的ルートでリソースが存在しない場合の処理パターンを示す。

nextjsroutingerror-handling

対応バージョン

nextjs 15react 19

前提環境

Next.js App Router の動的ルート([slug] など)の基本を理解していること

概要

notFound() 関数と not-found.tsx を組み合わせてカスタム 404 ページを実装する。動的ルートでリソースが存在しない場合に notFound() を呼び出すことで、not-found.tsx に制御を渡す。error.tsx(ランタイムエラー用)とは責務が異なる。

インストール

# 追加インストールは不要

実装

カスタム 404 ページ

// app/not-found.tsx
import Link from "next/link";

export default function NotFound() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-center p-8 text-center">
      <h1 className="mb-2 text-6xl font-bold text-gray-200">404</h1>
      <h2 className="mb-4 text-2xl font-semibold text-gray-800">
        ページが見つかりません
      </h2>
      <p className="mb-8 text-gray-500">
        お探しのページは存在しないか、削除された可能性があります。
      </p>
      <Link
        href="/"
        className="rounded bg-blue-600 px-6 py-2 text-sm text-white hover:bg-blue-700"
      >
        トップページへ戻る
      </Link>
    </main>
  );
}

動的ルートでの notFound() 呼び出し

// app/posts/[id]/page.tsx
import { notFound } from "next/navigation";

type Post = { id: number; title: string; body: string };

async function getPost(id: number): Promise<Post | null> {
  const res = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${id}`,
    { next: { revalidate: 60 } }
  );
  if (!res.ok) return null;
  return res.json();
}

type Props = {
  params: Promise<{ id: string }>;
};

export default async function PostPage({ params }: Props) {
  const { id } = await params;
  const post = await getPost(Number(id));

  // post が取得できない場合は not-found.tsx に制御を渡す
  if (!post) notFound();

  return (
    <article className="mx-auto max-w-2xl p-8">
      <h1 className="mb-4 text-2xl font-bold">{post.title}</h1>
      <p className="text-gray-600">{post.body}</p>
    </article>
  );
}

セグメント固有の not-found.tsx(任意)

セグメント配下専用の 404 画面を設ける場合は、ルートと同じディレクトリに配置する。

// app/posts/[id]/not-found.tsx
import Link from "next/link";

export default function PostNotFound() {
  return (
    <main className="mx-auto max-w-xl p-8 text-center">
      <h2 className="mb-4 text-xl font-semibold text-gray-800">
        この記事は存在しません
      </h2>
      <Link href="/posts" className="text-blue-600 hover:underline">
        記事一覧へ戻る
      </Link>
    </main>
  );
}

ポイント

  • notFound()next/navigation からインポートし、Server Component 内で呼び出す。呼び出し後のコードは実行されない
  • app/not-found.tsx はアプリ全体のフォールバック 404 ページ。セグメント直下に置くと、そのルート専用の 404 ページになる
  • error.tsx はランタイムエラー(throw されたエラー)用、not-found.tsx は意図的な 404 用として使い分ける
  • Next.js 15 では paramsPromise 型のため await params が必要
  • generateStaticParams を使った静的生成ルートでは、存在しない ID にアクセスすると自動的に 404 になるが、明示的に notFound() を呼ぶことで動的レンダリング時も同じ挙動を保証できる

注意点

error.tsx はランタイムエラー用。not-found.tsx は意図的な 404 用として使い分ける。nextjs-error-boundary とは責務が異なる。

関連サンプル