ページの作成
親となるページを選択してください。
親ページに紐づくページを子ページといいます。
例: 親=スポーツ, 子1=サッカー, 子2=野球
子ページを親ページとして更に子ページを作成することも可能です。
例: 親=サッカー, 子=サッカーのルール
親ページはいつでも変更することが可能なのでとりあえず作ってみましょう!
この記事の要点
- エラーは想定内と想定外に分けて考える。前者は戻り値、後者は例外で表現する。
- 入力の検証エラーを
throwすると画面ごと差し替わる。useActionStateで受け取ってメッセージとして出す。 error.tsxは区画ごとに置ける。壊れた部分だけを差し替え、周囲は生かしたままretryで復帰できる。- エラー境界は描画中の例外だけを捕まえる。クリック処理や非同期処理の失敗は自前で捕まえる。
- 「データが無い」は障害ではない。
notFound()を使って 404 として表現する。
エラー処理の設計は、フレームワークの機能を並べるだけでは決まりません。まず「その失敗は起きて当然か、起きてはいけないか」を分けることが出発点になります。App Router はこの 2 つに別々の受け皿を用意しており、取り違えると使い勝手が大きく損なわれます。
12 種類のエラー
| 想定内のエラー | 想定外の例外 | |
|---|---|---|
| 例 | 入力値の検証失敗、外部 API が 400 を返した、在庫切れ | DB 接続断、null 参照、想定していない型 |
| 表現方法 | 戻り値として返す | 例外を投げる |
| 受け取る側 | useActionState などの状態 | error.tsx のエラー境界 |
| ユーザーへの見せ方 | フォームの脇にメッセージ | その区画を代替 UI に差し替え |
この区別が守られていないと「メールアドレスの形式が違うだけで画面全体が『問題が発生しました』になる」という体験になります。ユーザーが直せるものは画面を壊さずに伝える、というのが基本方針です。
2想定内のエラーは返す
Server Function の中では、検証に失敗しても例外を投げず、メッセージを戻り値にします。
// app/actions.ts
'use server'
export async function createPost(prevState: any, formData: FormData) {
const title = formData.get('title')
const content = formData.get('content')
const res = await fetch('https://api.example.com/posts', {
method: 'POST',
body: JSON.stringify({ title, content }),
})
if (!res.ok) {
return { message: '投稿の作成に失敗しました' }
}
}
受け取る側は useActionState です。返した値がそのまま state に入ります。
'use client'
import { useActionState } from 'react'
import { createPost } from '@/app/actions'
const initialState = { message: '' }
export function Form() {
const [state, formAction, pending] = useActionState(createPost, initialState)
return (
<form action={formAction}>
<input type="text" name="title" required />
<textarea name="content" required />
{state?.message && <p aria-live="polite">{state.message}</p>}
<button disabled={pending}>作成</button>
</form>
)
}
aria-live を付けておくと、スクリーンリーダーが変化を読み上げます。エラーメッセージは視覚以外にも届く必要があります。
サーバーコンポーネントでの取得に失敗した場合も同様で、条件分岐で代替表示を返すか、redirect するのが素直です。
3想定外の例外はエラー境界へ
バグや障害に由来する例外は、投げてエラー境界に任せます。error.tsx を置いた区画が受け皿になり、そこから内側だけが代替 UI に置き換わります。
// app/dashboard/error.tsx
'use client' // エラー境界は必ずクライアントコンポーネント
import { useEffect } from 'react'
export default function ErrorPage({
error,
retry,
}: {
error: Error & { digest?: string }
retry: () => void
}) {
useEffect(() => {
console.error(error) // 監視サービスへ送るならここ
}, [error])
return (
<div>
<h2>問題が発生しました</h2>
<button onClick={() => retry()}>再試行</button>
</div>
)
}
retry を呼ぶと、その区画の取得と描画をやり直します。一時的な通信エラーであれば、ページ全体を再読み込みせずに復帰できます。
error オブジェクトの digest は、本番環境で詳細メッセージが隠されるときにサーバー側ログと突き合わせるための識別子です。画面には「問題が発生しました(コード: xxxx)」のように出しておくと、問い合わせ対応が楽になります。
4コンポーネント単位の境界
ファイル規約はルートの区画にしか置けません。「サイドバーのウィジェットだけを守りたい」といった細かい単位には catchError を使います。
// app/custom-error-boundary.tsx
'use client'
import { catchError, type ErrorInfo } from 'next/error'
function ErrorFallback(props: { title: string }, { error, retry }: ErrorInfo) {
return (
<div>
<h2>{props.title}</h2>
<p>{error.message}</p>
<button onClick={() => retry()}>再試行</button>
</div>
)
}
export default catchError(ErrorFallback)
返ってきたコンポーネントは、任意の場所でラッパーとして使えます。
import ErrorBoundary from './custom-error-boundary'
export default function Component({ children }: { children: React.ReactNode }) {
return <ErrorBoundary title="ダッシュボードのエラー">{children}</ErrorBoundary>
}
5ルートレイアウトが壊れたとき
ルートレイアウト自体で例外が出ると、通常のエラー境界では受け止められません。このために app/global-error.tsx があります。ルートレイアウトを置き換える形で表示されるため、自分で html と body を書く必要があります。
// app/global-error.tsx
'use client'
export default function GlobalError({
error,
retry,
}: {
error: Error & { digest?: string }
retry: () => void
}) {
return (
<html>
<body>
<h2>問題が発生しました</h2>
<button onClick={() => retry()}>再試行</button>
</body>
</html>
)
}
ここが表示される状況はアプリ全体が機能していない状態なので、凝った UI よりも「再読み込み」と「連絡先」が示せれば十分です。
6エラー境界が捕まえないもの
エラー境界は描画中の例外を対象とします。クリックハンドラの中や、描画後に走る非同期処理での失敗は対象外です。
'use client'
import { useState } from 'react'
export function Button() {
const [error, setError] = useState(null)
const handleClick = () => {
try {
// 失敗するかもしれない処理
} catch (reason) {
setError(reason)
}
}
if (error) {
// 代替 UI を描画する
}
return <button type="button" onClick={handleClick}>実行</button>
}
例外は useState に保持し、表示を切り替える形にします。ただし useTransition の startTransition 内で発生した未処理の例外は、最も近いエラー境界まで伝播します。
7404 は例外ではない
「指定された ID の記事が存在しない」は障害ではありません。notFound() を呼べば、最も近い not-found.tsx が表示されます。
import { notFound } from 'next/navigation'
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const post = await getPostBySlug(slug)
if (!post) {
notFound()
}
return <div>{post.title}</div>
}
これをエラー境界に落とすと、サーバー側では 500 系として扱われ、検索エンジンにも意図しない信号を送ることになります。SEO の観点でも、存在しないページは 404 として返すのが正しい振る舞いです。
8つまずきやすいところ
error.tsxが効かない:'use client'の付け忘れが最頻出です。- 入力エラーで画面が吹き飛ぶ:想定内のエラーを
throwしています。戻り値に変えます。 - 本番でエラー内容が出ない:情報漏えいを防ぐため、本番では詳細が伏せられます。
digestを頼りにサーバーログを確認します。 redirectをtry/catchで囲む:redirectは制御用の例外を投げるため、包むと遷移が止まります。catchの外に出してください。
画面側の作りは一通り揃いました。次は、外部から呼ばれる API を用意する方法に進みます。
ページの作成
親となるページを選択してください。
親ページに紐づくページを子ページといいます。
例: 親=スポーツ, 子1=サッカー, 子2=野球
子ページを親ページとして更に子ページを作成することも可能です。
例: 親=サッカー, 子=サッカーのルール
親ページはいつでも変更することが可能なのでとりあえず作ってみましょう!
子ページはありません
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 Spring Frameworkのアノテーション一覧
- 7 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- PHPでのファイルアップロード方法|multipart/form-dataと$_FILES 2026-08-07 17:15:57
- PHP $_SERVERとは|DOCUMENT_ROOT・PHP_SELFなど主な要素 2026-08-07 17:15:57
- Scratchのアカウントの作り方|メールアドレスとユーザー名の登録手順 2026-08-07 17:15:57
- djangoにおけるMVCアプリケーション実装例 2026-08-07 17:15:57
- Bluetoothとは|仕組み・BLEとClassicの違い・用途 2026-08-07 17:15:57
- Spring Bootのテスト入門|@SpringBootTestとJUnitでの単体・結合テスト 2026-08-07 17:15:57
- Java とは?言語仕様・JVM・主要フレームワーク一覧 2026-08-07 17:15:56
- インストール方法(Windows版) 2026-08-07 17:15:56
- APIキー取得方法 2026-08-07 17:15:56
- 【PHPフレームワーク】Laravelの使い方 2026-08-07 17:15:56
- Scratch 作品の公開方法 2026-08-07 17:15:56
- Google OAuth 2.0 認証の実装方法 2026-08-07 17:15:56
- データベースとは?RDB / NoSQL / SQL の基礎 2026-08-07 17:15:56
- インストール(eclipseプラグイン) 2026-08-07 17:15:56
- Spring Frameworkの使い方 2026-08-07 17:15:56
コメントを削除してもよろしいでしょうか?