【Next.js】404 ページの作り方を not-found.tsx と notFound() の使い方まで解説
公開日: 2026年10月8日 · 読了目安 8 分
Next.js の App Router で、独自の 404 ページを作る手順です。app/not-found.tsx の置き方、データがないときの notFound() の呼び方、フォルダごとの 404、タイトルと noindex の扱い、ページに置く中身、多言語サイトでの注意点まで、コード付きで解説します。
Next.js の 404 ページを自分のデザインに変える
Next.js でサイトを作り、試しに存在しない URL を開いた。すると白い画面に「404」と「This page could not be found.」だけが出た。
サイトの見た目と合わないうえ、読者はどこへ戻ればいいか分かりません。
直し方の答えは短いです。app フォルダの直下に not-found.tsx を1つ置けば、それが 404 ページになります。
記事や商品のデータが見つからないときは、ページの中で notFound() を呼びます。すると同じ 404 ページに切り替わります。
この記事で分かることは次のとおりです。
app/not-found.tsx で作る、いちばん基本の 404 ページ
データがないときに notFound() で 404 ページを出す方法
ブログなど、フォルダごとに別の 404 ページを出す方法
404 ページに置く中身と、控えめなアニメーション
多言語サイト([locale])での注意点
404 ページを作り込む3つの理由
404 ページに来るのは、リンク切れや打ち間違いでたどり着いた人です。サイトに興味を持って来たのに、行き止まりにぶつかった状態です。
離脱を減らす:ホームや人気ページへのリンクがあれば、そのまま読み進めてもらえる
ブランドを保つ:ヘッダーや色がサイトと揃っていれば、壊れたサイトに見えない
検索エンジンに正しく伝える:ページがないことを 404 として返せば、存在しない URL が検索結果に残りにくい
見た目だけ整えても、データがないページが普通のページとして返ると意味が薄れます。この記事では、見た目と仕組みの両方を順に作ります。
Step1:app/not-found.tsx で 404 ページを作る
not-found.tsx は、Next.js が役割を決めている特別なファイル名です。app の直下に置くと、どのページにも一致しない URL でこの画面が出ます。
サーバーで組み立てる部品(サーバーコンポーネント)のままで書けます。ルートのレイアウト(app/layout.tsx)の中に表示されるので、ヘッダーやフッターもそのまま付きます。
import Link from "next/link";
export default function NotFound() {
return (
<main className="mx-auto flex min-h-[70vh] max-w-xl flex-col items-center justify-center px-6 py-24 text-center">
<p className="text-sm font-semibold tracking-widest text-zinc-500">404</p>
<h1 className="mt-3 text-3xl font-semibold text-zinc-900">ページが見つかりません</h1>
<p className="mt-4 text-zinc-600">
URL が変わったか、ページが削除された可能性があります。
</p>
<Link
href="/"
className="mt-8 inline-flex min-h-11 items-center rounded-full bg-zinc-900 px-6 text-sm font-medium text-white hover:bg-zinc-700"
>
ホームに戻る
</Link>
</main>
);
}404 ページのタイトルを metadata で付ける
not-found.tsx でも、ページと同じように metadata を書き出せます。
ブラウザのタブに「ページが見つかりません」と出れば、読者も状況がすぐ分かります。
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "ページが見つかりません",
};404 ページのステータスと noindex は自動で付く
not-found.tsx が表示されるとき、Next.js はふつう 404 のステータスコードを返します。ステータスコードは、ページの有無をサーバーが伝える番号です。
あわせて、検索結果に載せないための noindex の meta タグも自動で入ります。自分で robots の設定を書く必要はありません。
例外は、loading.tsx などで画面を先に送り始めたあとに 404 になった場合です。このときステータスは 200 になりますが、noindex のタグは入ります。
Step2:データがないときに notFound() で 404 ページを出す
ブログの app/blog/[slug]/page.tsx は、どんな文字列の URL にも一致します。存在しない記事の URL でも、このページが開いてしまいます。
そこで、データが見つからないときは next/navigation の notFound() を呼びます。呼んだ時点で描画が止まり、404 ページに切り替わります。
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { cache } from "react";
import { getPostBySlug } from "@/lib/posts"; // 記事がなければ undefined を返す関数
// generateMetadata と page の取得を1回にまとめる
const getPost = cache(getPostBySlug);
type Props = { params: Promise<{ slug: string }> };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const post = await getPost(slug);
if (!post) notFound();
return { title: post.title, description: post.excerpt };
}
export default async function BlogPostPage({ params }: Props) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) notFound();
return (
<article className="mx-auto max-w-2xl px-6 py-24">
<h1 className="text-3xl font-semibold">{post.title}</h1>
<div className="mt-8 leading-relaxed text-zinc-700">{post.body}</div>
</article>
);
}notFound() のコードのポイント
Next.js 15 では params が Promise なので、await してから slug を取り出す
notFound() の後ろは実行されない。TypeScript も、その先では post があるものとして扱う
generateMetadata の中でも、同じように notFound() を呼べる
React の cache で包むと、タイトル用と本文用の取得が1回で済む
ブログだけ別の 404 ページにする
not-found.tsx は、フォルダごとに置けます。app/blog/not-found.tsx を置くと、ブログの中で notFound() を呼んだときはこちらが使われます。
いちばん近いフォルダのファイルが選ばれるので、記事一覧へ戻るリンクなど、場所に合った案内を出せます。
ただし、どのページにも一致しない URL を受け持つのは app 直下の not-found.tsx です。
例外は、15.4 以降の実験的な global-not-found.tsx を使う場合です。
import Link from "next/link";
export default function BlogNotFound() {
return (
<main className="mx-auto max-w-xl px-6 py-24 text-center">
<h1 className="text-2xl font-semibold text-zinc-900">記事が見つかりません</h1>
<p className="mt-4 text-zinc-600">記事の URL が変わったか、公開を終了した可能性があります。</p>
<Link
href="/blog"
className="mt-8 inline-flex min-h-11 items-center rounded-full bg-zinc-900 px-6 text-sm font-medium text-white"
>
記事の一覧へ
</Link>
</main>
);
}Step3:404 ページに置く中身を決める
404 ページの役目は、迷った人を次の行き先へ戻すことです。凝った演出より先に、次の3つを揃えます。
何が起きたかを短く伝える見出し(「ページが見つかりません」)
ホームへ戻るボタン
サイト内検索か、よく読まれているページへのリンク
検索は、GET で送るだけのフォームならサーバーで組み立てる部品のまま置けます。送信先の /search は、自分のサイトの検索ページに合わせて変えてください。
import Link from "next/link";
const popular = [
{ href: "/blog", label: "ブログ記事の一覧" },
{ href: "/pricing", label: "料金プラン" },
{ href: "/contact", label: "お問い合わせ" },
];
export default function NotFound() {
return (
<main className="mx-auto max-w-xl px-6 py-24 text-center">
<p className="text-sm font-semibold tracking-widest text-zinc-500">404</p>
<h1 className="mt-3 text-3xl font-semibold text-zinc-900">ページが見つかりません</h1>
<p className="mt-4 text-zinc-600">URL が変わったか、ページが削除された可能性があります。</p>
<form action="/search" method="get" role="search" className="mt-8 flex gap-2">
<label htmlFor="q" className="sr-only">サイト内を検索</label>
<input
id="q"
name="q"
type="search"
placeholder="キーワードで探す"
className="min-h-11 flex-1 rounded-full border border-zinc-300 px-4 text-base"
/>
<button type="submit" className="min-h-11 rounded-full bg-zinc-900 px-5 text-sm font-medium text-white">
検索
</button>
</form>
<ul className="mt-10 space-y-1 text-sm">
{popular.map((item) => (
<li key={item.href}>
<Link href={item.href} className="inline-flex min-h-11 items-center underline underline-offset-4">
{item.label}
</Link>
</li>
))}
</ul>
<Link
href="/"
className="mt-8 inline-flex min-h-11 items-center rounded-full border border-zinc-300 px-6 text-sm font-medium"
>
ホームに戻る
</Link>
</main>
);
}404 ページの文言は読者を責めない
「URL が間違っています」と書くと、読者のせいに聞こえます。リンク切れは、サイト側の都合でも起きます。
「見つかりません」「移動した可能性があります」のように、事実だけを伝えます。
イラストや遊びを入れる場合も、戻るためのリンクは最初に見える位置に置きます。
Step4:404 ページに控えめなアニメーションを付ける
大きな 404 の数字を、ふわっと浮かび上がらせるだけでも印象は変わります。動きは1回きりで、0.5秒前後に抑えます。
アニメーションには Motion(旧 Framer Motion)を使います。動く部分だけを別ファイルにして、先頭にブラウザで動かす印の "use client" を書きます。
"use client";
import { motion, useReducedMotion } from "motion/react";
export function NotFoundNumber() {
const reduceMotion = useReducedMotion();
return (
<motion.p
aria-hidden="true"
initial={reduceMotion ? false : { opacity: 0, y: 24 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="text-[8rem] font-semibold leading-none tracking-tight text-zinc-900 sm:text-[12rem]"
>
404
</motion.p>
);
}404 ページのアニメーションのポイント
not-found.tsx 自体はサーバーで組み立てる部品のままにして、この部品だけを読み込む
動きを減らす設定の人には initial を false にして、最初から表示した状態にする
大きな数字は飾りなので aria-hidden を付け、読み上げは h1 の見出しに任せる
Motion の基本は https://design-layer.com/ja/articles/nextjs-framer-motion-guide で解説しています。
多言語サイト([locale])で 404 ページを出すときの注意点
URL の先頭に言語が入るサイトでは、app/[locale] の下にページを置きます。この形では、404 ページの扱いが少し変わります。
app/[locale]/not-found.tsx は、その中で notFound() を呼んだときに表示されます。しかし、どのページにも一致しない URL では使われません。
そこで、残りの URL をすべて受け止めるページを1つ作り、その中で notFound() を呼びます。next-intl などの多言語ライブラリの説明でも使われている方法です。
なお、not-found.tsx は params を受け取れません。表示する言語の決め方は、使っている多言語ライブラリの方法に合わせてください。
import { notFound } from "next/navigation";
// どのページにも一致しない URL をここで受け止め、[locale] の not-found.tsx を出す
export default function CatchAllPage() {
notFound();
}404 ページでよくある失敗と公開前チェック
データがないのに空のページを返す。notFound() を呼ばないと、中身のないページが 200 のまま検索エンジンに届く
404 ページからホームへ自動で転送する。読者は何が起きたか分からず、ページがないことも伝わりにくい
戻るリンクがない、または画面の下の方にしかない
404 ページ全体に "use client" を書く。動く部分だけを切り出す
移動しただけのページを 404 にする。移動先があるなら next.config の redirects で転送する
公開前に、存在しない URL を開いて表示を確かめます。next build のあとの本番と同じ状態で試すと確実です。
ステータスは curl -I https://example.com/no-such-page のように確かめると、404 が返っているか分かります。
完成した 404 ページをコピーして使う方法

ここまでの手順で、仕組みとしての 404 ページはでき上がります。ただ、次のような見た目まで作り込むと時間がかかります。
紙のような質感の背景に、大きな 404 を置く
中央に石像の切り抜きを重ねる
文言やアクセント色をサイトに合わせる
そんなときは、完成した 404 画面をブラウザで確かめてから、コードを自分の Next.js に載せる方法もあります。
DesignLayer の「スタチュー 404」は、ざらついた紙地に巨大な 404 と石像の切り抜きを重ねたエラー画面です。文言・像の画像・アクセント色を差し替えて、not-found.tsx に置けます。
詳細ページの「Cursor 用にコピー」を押すと、統合の指示・ライブプレビューの URL・本体の TSX・npm の依存がまとめてコピーされます。Cursor や Claude に貼って、組み込みを頼めます。
閲覧とライブプレビューは無料で、アカウントもカードも要りません。無料のソースコピーは日本時間で1日3回です。
カタログは https://design-layer.com/ja/components から開けます。
Next.js の 404 ページについてよくある質問
App Router では、app フォルダの直下に not-found.tsx を置きます。Pages Router の pages/404.tsx とはファイルの場所が違います。
まとめ:Next.js で 404 ページを作る手順
app 直下に not-found.tsx を置くと、一致しない URL でその画面が 404 ページとして出る
データがないときは notFound() を呼ぶ。404 のステータスと noindex は Next.js が付ける
フォルダごとに not-found.tsx を置くと、場所に合った 404 ページを出せる
中身は見出し・ホームへのリンク・検索か人気ページの3つを先に揃える
[locale] のサイトでは、残りの URL を受け止めるページで notFound() を呼ぶ
まずは Step1 のコードを app 直下に置き、存在しない URL を開いてみてください。
データを待つあいだの画面は https://design-layer.com/ja/articles/nextjs-loading-screen-guide で解説しています。