言語を切り替える
テーマを切り替える

Next.js 404 & 500 エラーページ完全カスタマイズガイド:技術実装からデザイン最適化まで

金曜日の午後3時、プロダクトマネージャーがチャットにスクリーンショットを貼りました。「これ、うちのサイト? さすがに酷くない?」

開いてみると、そこには白背景に黒文字で、飾り気のない「404 This page could not be found」の文字が。気まずい空気が流れました。

正直なところ、Next.js プロジェクトに取り組む際、私たちは常に「正常な」ページに集中しがちです。トップページは美しく、リストはスムーズに、詳細は完璧に。エラーページ? 誰も気にしないでしょう、どうせ滅多に見ないんだから。

しかし、データは残酷でした。デフォルトの404ページを見たユーザーの40%が、そのままタブを閉じていたのです。

この数字で目が覚めました。エラーページは「あってもなくてもいい飾り」ではなく、ユーザーを繋ぎ止める最後のチャンスなのです。リンク切れでサイトに来たユーザーが、ナビゲーションも検索窓もヒントもない、冷たい「ページが見つかりません」という白いページを見たらどう思うでしょうか?「このサイト、大丈夫か?」と思うはずです。

幸い、Next.js App Router には完全なエラー処理メカニズムが用意されています。404を処理する not-found.tsx、実行時エラーを処理する error.tsx、そしてアプリ全体をカバーする global-error.tsx です。簡単そうに聞こえますか? 実は結構な落とし穴があります。

私が最初に設定した時、HTTPステータスコードが404ではなく頑なに200を返し続け、Googleが私の404ページをインデックスしてしまったことがありました。またある時は、global-error.tsx のスタイルが全く効かず、ドキュメントを読み漁った結果、CSSモジュールのインポートをサポートしていないことに気づいたこともあります。

この記事では、Next.js のエラーページを完全に攻略する方法を解説します。not-found.tsx の基本的な使い方から、error.tsx のエラー境界(Error Boundary)、そしてユーザーを逃さない404ページのデザインまで。実用的なコードと、私が踏み抜いた落とし穴の回避策をすべて共有します。

Next.js エラー処理メカニズム全解剖

App Router を触り始めた頃、これら3つのファイルの違いがよく分かりませんでした。not-found.tsxerror.tsxglobal-error.tsx。名前は似ていますが、役割は全く異なります。

3つのエラーファイルの役割分担

簡単に言うとこうなります:

  • not-found.tsx - 404専用。ページが存在しない時に表示。
  • error.tsx - 実行時エラー用。データ取得の失敗やコードのバグなど。
  • global-error.tsx - 最後の砦。Root Layout さえも機能しない場合に発動。

「なぜ3つも必要なの? error.tsx 1つじゃダメ?」と思うかもしれません。

実は、Next.js のエラー処理はマトリョーシカのような階層構造になっています。error.tsx は、同じ階層かそれ以下のルートのエラーしか捕捉できません。自分が配置されている layout.tsx のエラーは捕捉できないのです。もしRoot Layout自体が壊れたら? そこで global-error.tsx の出番です。

そして not-found.tsx は特殊な立ち位置で、error.tsx よりも優先度が高いです。notFound() 関数を呼び出すと、Next.js は error.tsx をスキップして直接 not-found.tsx をレンダリングします。

配置場所が重要

これら3つのファイルは異なるルート階層に配置でき、その配置場所が影響範囲を決定します。

ルートレベルのエラーファイルapp/ 直下):

app/
├── layout.tsx
├── not-found.tsx        ← 全体の 404 ページ
├── error.tsx            ← 全体のエラー処理
├── global-error.tsx     ← Root Layout のバックアップ
└── page.tsx

ルート固有のエラーファイル(特定のディレクトリ下):

app/
├── blog/
87: │   ├── [slug]/
88: │   │   ├── page.tsx
89: │   │   ├── not-found.tsx    ← ブログ記事専用 404
90: │   │   └── error.tsx         ← ブログ専用エラーページ

ユーザーが /blog/non-existent-post にアクセスした場合、Next.js はルートの app/not-found.tsx ではなく、優先して app/blog/[slug]/not-found.tsx を表示します。これにより、モジュールごとに異なるテイストのエラーページを作ることができます。

notFound() 関数:プログラムから 404 をトリガーする

not-found.tsx ファイルがあるだけでは不十分です。いつそれをトリガーするかを知っておく必要があります。

最も一般的なシナリオは、IDに基づいてデータを取得したが、データが存在しなかった場合です。

// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'

async function getPost(slug: string) {
  const res = await fetch(`https://api.example.com/posts/${slug}`)
  if (!res.ok) return null
  return res.json()
}

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)

  if (!post) {
    notFound()  // not-found.tsx をトリガー
  }

  return <article>{post.title}</article>
}

落とし穴注意:必ずJSXを返すnotFound() を呼び出してください。もし一部のコンテンツを返してから呼び出すと、既にストリーミングレスポンスが始まっており、HTTPステータスコードが404ではなく200に固定されてしまいます。

私が最初にハマったのがこれです:

// ❌ 間違い
export default async function Page({ params }) {
  const data = await fetchData(params.id)

  return (
    <div>
      {/* 既に JSX に入っている! */}
      {!data ? notFound() : <Content data={data} />}
    </div>
  )
}

結果、画面は404ページなのにステータスコードは200。検索エンジンはこれを正常なページとしてインデックスしてしまい、SEOが台無しになります。

正解

// ✅ 正解
export default async function Page({ params }) {
  const data = await fetchData(params.id)

  if (!data) {
    notFound()  // 先に判断、先に呼び出し
  }

  return <Content data={data} />  // データがある場合のみ JSX を返す
}

データを検証し、問題があれば即座に notFound()。JSXはその次です。これで正しい404ステータスが返ります。

not-found.tsx:カスタム404ページ実践

では、実際に作っていきましょう。まずは基礎的な404ページから始め、徐々に機能を追加します。

基礎編:とりあえず動くもの

最もシンプルな not-found.tsx はこんな感じです:

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

export default function NotFound() {
  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-50">
      <div className="text-center">
        <h1 className="text-6xl font-bold text-gray-900 mb-4">404</h1>
        <p className="text-xl text-gray-600 mb-8">
          お探しのページは見つかりませんでした
        </p>
        <Link
          href="/"
          className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
        >
          トップへ戻る
        </Link>
      </div>
    </div>
  )
}

ファイルを保存し、存在しないパス(例:http://localhost:3000/nowhere)にアクセスすれば確認できます。

デフォルトの画面よりはマシですが、まだシンプルすぎます。「トップへ戻る」しかないと、特定の情報を探しているユーザーにとっては不親切です。

発展編:ユーザーに選択肢を与える

優れた404ページは、複数の「出口」を用意します。私は通常、以下の要素を追加します:

  1. 検索ボックス - 自分で探してもらう
  2. 人気リンク - 興味のありそうな場所へ誘導する
  3. ブランド要素 - ロゴやブランドカラーで安心感を与える

完全なコードはこちら:

// app/not-found.tsx
'use client'

import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useState } from 'react'

export default function NotFound() {
  const router = useRouter()
  const [searchQuery, setSearchQuery] = useState('')

  const handleSearch = (e: React.FormEvent) => {
    e.preventDefault()
    if (searchQuery.trim()) {
      router.push(`/search?q=${encodeURIComponent(searchQuery)}`)
    }
  }

  const popularLinks = [
    { href: '/blog', label: '技術ブログ' },
    { href: '/projects', label: 'プロジェクト一覧' },
    { href: '/about', label: '私たちについて' },
  ]

  return (
    <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100">
      <div className="max-w-2xl w-full px-6 py-12 text-center">
        {/* 大きな 404 */}
        <h1 className="text-9xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-indigo-600 mb-4">
          404
        </h1>

        {/* 親しみやすいメッセージ */}
        <p className="text-2xl font-medium text-gray-800 mb-2">
          あら、ページが迷子のようです
        </p>
        <p className="text-gray-600 mb-8">
          リンクが有効期限切れか、ページが移動した可能性があります。<br/>
          でも大丈夫、以下の方法で探してみましょう:
        </p>

        {/* 検索ボックス */}
        <form onSubmit={handleSearch} className="mb-8">
          <div className="flex gap-2 max-w-md mx-auto">
            <input
              type="text"
              value={searchQuery}
              onChange={(e) => setSearchQuery(e.target.value)}
              placeholder="お探しのコンテンツを検索..."
              className="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
            />
            <button
              type="submit"
              className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium"
            >
              検索
            </button>
          </div>
        </form>

        {/* おすすめリンク */}
        <div className="mb-8">
          <p className="text-sm text-gray-600 mb-4">または人気のページへ:</p>
          <div className="flex flex-wrap justify-center gap-3">
            {popularLinks.map((link) => (
              <Link
                key={link.href}
                href={link.href}
                className="px-5 py-2 bg-white text-gray-700 rounded-lg border border-gray-200 hover:border-blue-500 hover:text-blue-600 transition-colors"
              >
                {link.label}
              </Link>
            ))}
          </div>
        </div>

        {/* ホームへ戻る */}
        <Link
          href="/"
          className="inline-flex items-center gap-2 text-blue-600 hover:text-blue-700 font-medium"
        >
          <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
          </svg>
          ホームへ戻る
        </Link>
      </div>
    </div>
  )
}

ファイルの先頭に 'use client' があることに注目してください。検索ボックスで useStateuseRouter を使うため、クライアントコンポーネントである必要があります。

これでユーザーは:

  • 欲しい情報を検索できる
  • 興味のありそうなリンクをクリックできる
  • ホームに戻ることもできる

離脱率は大幅に下がるはずです。

上級テクニック:404エラーの追跡

ユーザーがどんな「存在しないページ」にアクセスしているかを知りたくありませんか? それが分かれば、作成すべきコンテンツのヒントになります。

'use client'

import { useEffect } from 'react'
import { usePathname } from 'next/navigation'

export default function NotFound() {
  const pathname = usePathname()

  useEffect(() => {
    if (typeof window !== 'undefined') {
      // Google Analytics の例
      window.gtag?.('event', 'page_not_found', {
        page_path: pathname,
      })

      // または自社サーバーへ送信
      fetch('/api/analytics/404', {
        method: 'POST',
        body: JSON.stringify({ path: pathname }),
      }).catch(() => {}) // 失敗してもユーザー体験には影響させない
    }
  }, [pathname])

  return (
    // ...あなたの 404 UI
  )
}

データを分析すると、意外な発見があるかもしれません:

  • 削除した旧ページのURLにアクセスが多い → 301リダイレクトを設定すべき
  • 特定のスペルミスが多い → 自動補正を検討
  • 検索されているのに存在しないコンテンツ → 新記事のネタ

error.tsx と global-error.tsx:500エラーの処理

not-found.tsx は「無い」場合だけです。では、コードが爆発したり、APIがダウンしたり、データベースが応答しない場合は? ここで error.tsx の出番です。

error.tsx の基本

error.tsx は必ずクライアントコンポーネントでなければなりません('use client')。React の Error Boundary はクライアントでしか動作しないからです。

// app/error.tsx
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-50">
      <div className="max-w-md w-full px-6 py-8 bg-white rounded-lg shadow-lg">
        <div className="text-center">
          <div className="text-6xl mb-4">⚠️</div>
          <h2 className="text-2xl font-bold text-gray-900 mb-2">エラーが発生しました</h2>
          <p className="text-gray-600 mb-6">
            ページ読み込み中に予期せぬ問題が発生しました
          </p>

          <button
            onClick={() => reset()}
            className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium"
          >
            再試行
          </button>

          <Link
            href="/"
            className="block mt-4 text-sm text-gray-500 hover:text-gray-700"
          >
            トップへ戻る
          </Link>
        </div>
      </div>
    </div>
  )
}

重要なのは2つのpropsです:

  • error: 捕捉されたエラーオブジェクト。messagedigest(エラーハッシュ)を含みます。
  • reset: 関数。これを呼ぶと、エラーが発生したルートセグメントを再レンダリングし、復旧を試みます。

「再試行」ボタンをクリックすると reset() が走り、もし一時的なネットワークエラーなら、これで直るかもしれません。

本番環境でのエラー情報

セキュリティ上の理由から、開発環境では error.message に完全なエラー詳細(例:「Database connection failed: invalid credentials」)が表示されますが、本番環境では Next.js が自動的に情報を隠蔽します。

本番での error オブジェクトには以下が含まれます:

  • message: 汎用的なメッセージ(詳細は非表示)
  • digest: エラーのハッシュ値(ログ照合用)

詳細なログはサーバー側に記録されます。ユーザーには digest を表示し、それを報告してもらうことで、サーバーログから原因を特定できます。

'use client'

export default function Error({ error }: { error: Error & { digest?: string } }) {
  return (
    <div>
      <h2>エラーが発生しました</h2>
      <p>{error.message}</p>
      {error.digest && (
        <p className="text-xs text-gray-400 mt-4">
          エラーID: {error.digest}
        </p>
      )}
    </div>
  )
}

ユーザーから「エラーID: abc12345」と連絡があれば、サーバーログでそのIDを検索すれば、スタックトレース全体が見つかります。

エラーを監視サービスに記録する

ユーザーからの報告を待つべきではありません。Sentry や Datadog などの監視サービスに能動的に送信しましょう。

'use client'

import { useEffect } from 'react'
import * as Sentry from '@sentry/nextjs'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    // Sentry にエラーを送信
    Sentry.captureException(error)
  }, [error])

  return (
    <div className="min-h-screen flex items-center justify-center">
      <div className="text-center">
        <h2>問題が発生しました</h2>
        <button onClick={() => reset()}>再試行</button>
      </div>
    </div>
  )
}

これで、本番環境でエラーが起きれば5分以内に通知が来るようになります。

global-error.tsx:最後の砦

error.tsx は強力ですが、弱点があります。自分と同じ階層にある layout.tsx のエラーは捕捉できません。

そこで global-error.tsx です。これはアプリケーション全体をラップし、Root Layout のエラーさえも捕捉します。

// app/global-error.tsx
'use client'

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <html>
      <body>
        <div style={{ padding: '50px', textAlign: 'center' }}>
          <h2>深刻なエラーが発生しました</h2>
          <p>現在復旧作業中です。しばらくしてからお試しください。</p>
          <button onClick={() => reset()}>再読み込み</button>
        </div>
      </body>
    </html>
  )
}

重要な3つのポイント

  1. <html><body> タグを含める
    Root Layout が壊れているため、global-error.tsx はそれを完全に置き換えます。完全なHTML構造を自分で提供する必要があります。

  2. CSSモジュールやグローバルスタイルのインポート不可
    Next.js は global-error.tsx 内のCSSインポートを無視します。インラインスタイルか <style> タグを使うしかありません。

  3. 発生頻度は低い
    Root Layout は通常シンプルなので、めったに壊れません。global-error.tsx は「保険」のようなものです。

めったに使われないとしても、真っ白な画面よりはマシなので、作成しておくことを強くお勧めします。

完全な global-error.tsx の例

スタイルを少し整えて、見られるものにしましょう。

// app/global-error.tsx
'use client'

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <html>
      <body>
        <style>{`
          * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
          }
          body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
          }
          .container {
            text-align: center;
            color: white;
            padding: 2rem;
          }
          h2 {
            font-size: 2.5rem;
            margin-bottom: 1rem;
          }
          p {
            font-size: 1.2rem;
            margin-bottom: 2rem;
            opacity: 0.9;
          }
          button {
            padding: 12px 32px;
            font-size: 1rem;
            background: white;
            color: #667eea;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-weight: 600;
          }
          button:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
          }
        `}</style>

        <div className="container">
          <h2>😵 システムエラー</h2>
          <p>申し訳ありません、予期せぬ問題が発生しました。<br/>現在チームに通知され、対応中です。</p>
          <button onClick={() => reset()}>再読み込み</button>
          <p style={{ fontSize: '0.875rem', marginTop: '2rem', opacity: 0.7 }}>
            エラーID: {error.digest || 'unknown'}
          </p>
        </div>
      </body>
    </html>
  )
}

Tailwindは使えませんが、<style> タグでなんとか見栄えを整えられます。

エラーページデザインのベストプラクティス

技術的な実装は終わりましたが、ここからが本番です。技術は機能を提供しますが、ユーザーを留めるのはデザインです。

Spotify、Figma、Mailchimp などの大手企業の404ページを分析し、共通の成功要因をまとめました。

必須要素:ユーザーに「出口」を与える

合格点の404ページには、少なくとも以下が必要です:

1. 明確だが威圧的でない説明

❌ ダメな例:

Error 404: The requested resource could not be located on the server.

誰に向けて話しているのでしょう? ユーザーは直感的に「壊れてる、怖い」と感じます。

✅ 良い例:

あら、ページが見つかりません
リンクが古いか、移動した可能性があります。

普通の言葉で話しましょう。

2. メインナビゲーションまたはホームへのリンク

最低限の「避難経路」です。

<Link href="/" className="text-blue-600">ホームへ戻る</Link>

3. 検索ボックス

URLの打ち間違いやリンク切れの可能性が高いです。検索窓があれば、ユーザーはすぐに目的のコンテンツを探し始められます。Spotifyの404ページには巨大な検索窓があり、「Search for what you’re looking for」と書かれています。

4. おすすめコンテンツ

せっかく来てくれたのですから、手ぶらで帰すのはもったいないです。

  • ブログなら「最新記事」
  • ECなら「人気商品」
  • SaaSなら「機能紹介」

Netflixの404ページは人気作品を並べています。ユーザーは間違いで来たことを忘れ、思わずドラマを見始めてしまうでしょう。

5. ブランドの一貫性

ロゴ、配色、フォントはサイトの他の部分と同じにしてください。
真っ白で無機質なエラーページは、「怪しいサイトに来てしまったか?」と不安を与えます。

デザイン戦略:気まずさを和らげる

ユーモアを取り入れる

Figmaの404ページでは、UIパーツが画面上を動き回り、クリックしても形が変わるという遊び心があります。「Hmm, we can’t find that page.」という軽いトーンです。
「致命的なエラー」ではなく「ちょっとした迷子」という雰囲気を出すことで、ユーザーのストレスを軽減できます。
ただし、金融や医療など信頼性が重要なサイトでは慎重に。

モバイル最適化を忘れない

トラフィックの40%以上はモバイルです。

  • ボタンは指で押しやすい大きさに(最低44x44px)
  • テキストは簡潔に
  • 重要なリンクはファーストビューに

私が以前見た最悪の404ページは、デスクトップでは綺麗でしたが、スマホでは「ホームへ戻る」ボタンが米粒ほどで、3回タップしてやっと押せました。

実際のデータ

私のブログでA/Bテストを行った結果です:

バージョン A(デフォルトの404):

  • 直帰率: 78%
  • 平均滞在時間: 3秒

バージョン B(検索窓 + おすすめ記事付きカスタム404):

  • 直帰率: 42%
  • 平均滞在時間: 35秒

直帰率が半減しました。20%のユーザーがおすすめ記事をクリックし、読書を続けました。
これがデザインの力です。「ページがない」という事実は同じでも、ユーザー体験は天と地ほど違います。

よくある質問とトラブルシューティング

最後に、私がハマった落とし穴と解決策をまとめておきます。

問題1: notFound() を呼んでいるのにステータス200が返る

症状:
404ページは表示されているのに、ブラウザのネットワークタブを見るとステータスコードが200。Googleに正常ページとしてインデックスされてしまう。

原因:
ストリーミングレスポンスが既に始まってから notFound() を呼んでいる(=JSXを返し始めている)ため。

解決策:
JSXを返すnotFound() を呼び出す。

// ❌ ダメ:JSX リターンの一部として呼んでいる
return <div>{!data ? notFound() : <Content data={data} />}</div>

// ✅ OK:JSX の前に呼んでいる
if (!data) notFound()
return <Content data={data} />

先判定、先呼び出し」が鉄則です。

問題2: global-error.tsx にスタイルが当たらない

症状:
Tailwind や CSS Modules をインポートしたのに、真っ白で文字だけのページになる。

原因:
Next.js の仕様により、global-error.tsx はCSSインポートを無視します。

解決策:
インラインスタイルか <style> タグを使う。泥臭いですが、これしかありません。

問題3: ネストした not-found.tsx が効かない

症状:
app/blog/[slug]/not-found.tsx を作ったのに、/blog/nothing にアクセスするとルートの404が表示される。

原因:
page.tsxnotFound() を呼び出していないか、ファイルの配置が間違っている。

解決策:
ディレクトリ構造を確認し、対応する page.tsx で意図的に notFound() を呼び出してください。単にURLがマッチしない(/blog/undefined-route)場合はルートの404が出ますが、/blog/[slug] 内のロジックで404にする場合は、そのディレクトリの not-found.tsx が使われます。

問題4: error.tsx がエラーを捕捉しない

症状:
エラーが起きているのに白画面になる、またはルートのエラーが出る。

原因:
エラーが error.tsx と同じ階層の layout.tsx で起きている。error.tsx同階層の Layout のエラーは捕捉できません

解決策:
一つ上の階層に error.tsx を置くか、ルートの global-error.tsx に任せる。

問題5: 本番環境でエラーメッセージが見えない

症状:
error.message が “Application error” としか表示されない。

原因:
Next.js のセキュリティ仕様。

解決策:
error.digest を表示し、それを使ってサーバーログを検索する。または Sentry 等のツールに自動送信する。

結論

Next.js のエラー処理は3層構造です:

  • not-found.tsx → 404(存在しない)
  • error.tsx → 500(実行時エラー)
  • global-error.tsx → システム崩壊(ルートレイアウト死亡)

技術的な実装は難しくありません。勝負はデザインです。検索窓を置き、おすすめリンクを並べ、少しの人間味を加えるだけで、78%の直帰率を42%まで下げることができます。

今すぐあなたのサイトの404ページを確認してみてください。デフォルトのままなら、ユーザーをみすみす逃しています。30分かけてカスタマイズすれば、その効果はずっと続きますよ。

Next.js カスタム404ページの作成

Next.js App Router で検索機能と推奨コンテンツを備えた高機能なカスタム404ページを作成する手順

  1. 1

    Step1: not-found.tsx の作成

    app ディレクトリの直下に not-found.tsx ファイルを作成します。これがグローバルな404ページになります。
  2. 2

    Step2: 基礎UIの構築

    Next.js の Link コンポーネントをインポートし、エラーメッセージとホームに戻るボタンを含む基本的なレイアウトを作成します。
  3. 3

    Step3: クライアント機能の有効化

    検索機能などのインタラクティブな要素を追加する場合、ファイルの先頭に 'use client' を追加します。
  4. 4

    Step4: 検索機能の実装

    useState と useRouter を使用して、ユーザーがコンテンツを検索できるフォームを追加します。
  5. 5

    Step5: おすすめリンクの追加

    人気のあるページへのリンク集を追加し、迷子になったユーザーを誘導します。
  6. 6

    Step6: スタイリング

    Tailwind CSS などを使用してデザインを整え、サイトの他の部分とブランドイメージを統一します。
  7. 7

    Step7: 404トリガーの実装

    動的ルート([slug]/page.tsxなど)で、データが存在しない場合に notFound() 関数を呼び出すロジックを追加します。
  8. 8

    Step8: 動作確認

    存在しないURLにアクセスし、デザインとHTTPステータスコード(404であること)を確認します。

FAQ

Next.js の not-found.tsx、error.tsx、global-error.tsx の違いは?
not-found.tsx は404(ページ不在)専用です。error.tsx はコンポーネントツリー内の実行時エラーを捕捉します。global-error.tsx は Root Layout レベルの致命的なエラーを捕捉するための最終バックアップです。
notFound() を呼んでもステータスコードが200になるのはなぜ?
JSX(コンポーネントのレンダリング結果)を返し始めた後に notFound() が呼ばれているためです。ストリーミングが始まるとヘッダーを変更できません。必ず JSX をリターンする前にデータを検証し、notFound() を呼び出してください。
global-error.tsx で Tailwind CSS が効かないのはなぜ?
Next.js は global-error.tsx 内でのグローバル CSS や CSS モジュールのインポートを無視します(ルートレイアウトを置換するため)。インラインスタイルか <style> タグを使用する必要があります。
404ページへのアクセスを分析ツールで追跡するには?
not-found.tsx 内(クライアントコンポーネント化が必要)で useEffect と usePathname を使用し、ページロード時に現在のパスを Google Analytics や自社サーバーに送信することで追跡できます。
ユーザーの離脱を防ぐ404ページのデザイン要素は?
1) 専門用語を使わない親しみやすい説明、2) ホームや主要ページへの明確なリンク、3) コンテンツを探せる検索ボックス、4) おすすめ・人気記事の表示、5) サイト全体と統一されたブランドデザイン、が重要です。

6 min read · 公開日: 2026年1月5日 · 更新日: 2026年1月22日

コメント

GitHubアカウントでログインしてコメントできます

関連記事