Next.js の redirect() と notFound() は例外を throw する
Next.js App Router の redirect()、notFound()、permanentRedirect() が例外を throw する仕組みと、try/catch ブロックでの正しい扱い方を解説します。
この記事は Next.js 16.1.6 時点での情報を元に書かれています。
この記事で学べること
redirect()、notFound()、permanentRedirect()の基本的な使い方- これらの関数が例外を throw する理由
- try/catch ブロックでの正しい扱い方
unstable_rethrowを使った解決方法
前提知識
- Next.js App Router の基本的な使い方
- JavaScript の try/catch 構文の理解
redirect(), notFound(), permanentRedirect() とは
Next.js App Router では、ページのリダイレクトや 404 エラーの表示を行うための関数が用意されています。これらはすべて next/navigation からインポートして使用します。
各関数の役割
| 関数 | 役割 |
|---|---|
redirect() | 指定した URL に一時的にリダイレクト |
permanentRedirect() | 指定した URL に恒久的にリダイレクト |
notFound() | 404 Not Found ページを表示 |
基本的な使い方
import { redirect, notFound, permanentRedirect } from "next/navigation";
// 一時的なリダイレクト
export default async function Page() {
const user = await getUser();
// ユーザーがログインしていなければログインページへリダイレクト
if (!user) {
redirect("/login");
}
return <div>ようこそ、{user.name} さん</div>;
}
import { notFound } from "next/navigation";
// 404 ページを表示
export default async function PostPage({ params }: { params: { id: string } }) {
const post = await getPost(params.id);
// 記事が見つからなければ 404 を表示
if (!post) {
notFound();
}
return <article>{post.content}</article>;
}
import { permanentRedirect } from "next/navigation";
// 恒久的なリダイレクト(URL が永久に変更された場合)
export default function OldPage() {
permanentRedirect("/new-page");
}
これらの関数は例外を throw する
redirect()、notFound()、permanentRedirect() は、通常の関数とは異なり、内部的に例外(Error)を throw します。これは Next.js の設計上の重要な特徴です。
| 関数 | エラーメッセージ |
|---|---|
redirect() | NEXT_REDIRECT |
permanentRedirect() | NEXT_REDIRECT |
notFound() | NEXT_HTTP_ERROR_FALLBACK;404 |
import { notFound, permanentRedirect, redirect } from "next/navigation"; try { redirect("/"); } catch (error) { console.error(error); // => Error: NEXT_REDIRECT } try { permanentRedirect("/"); } catch (error) { console.error(error); // => Error: NEXT_REDIRECT } try { notFound(); } catch (error) { console.error(error); // => Error: NEXT_HTTP_ERROR_FALLBACK;404 }
なぜ例外を throw するのか
これらの関数が例外を throw する理由は、React のレンダリングを即座に中断し、Next.js のフレームワークに制御を渡すためです。
通常の関数のように値を返すだけでは、その後のコードが実行されてしまいます。例外を throw することで、以下のことが可能になります。
- コンポーネントのレンダリングを即座に停止する
- Next.js がリダイレクトや 404 の処理を引き継ぐ
- 適切な HTTP レスポンスをクライアントに返す
try/catch ブロックでの問題点
redirect() や notFound() が例外を throw するという仕組みを理解すると、try/catch ブロック内で使用したときに問題が起きる理由がわかります。
問題が起きるコード例
以下のコードは、一見正しく見えますが、意図した通りに動作しません。
import { redirect } from "next/navigation";
export default async function Page() {
try {
const user = await getUser();
if (!user) {
// このリダイレクトは実行されない!
redirect("/login");
}
return <div>ようこそ、{user.name} さん</div>;
} catch (error) {
// redirect() が throw したエラーがここでキャッチされてしまう
console.error("エラーが発生しました:", error);
return <div>エラーが発生しました</div>;
}
}
なぜ問題が起きるのか
redirect("/login")が呼び出される- 内部的に
NEXT_REDIRECTエラーが throw される - catch ブロックがこのエラーをキャッチしてしまう
- Next.js にエラーが到達せず、リダイレクトが実行されない
- 代わりに「エラーが発生しました」が表示される
Next.js の公式ドキュメントでは、この点について明確に警告しています。
redirectthrows an error so it should be called outside thetryblock when usingtry/catchstatements.
解決方法 1: try/catch の外で呼び出す(推奨)
最もシンプルで推奨される解決方法は、redirect() や notFound() を try/catch ブロックの外で呼び出すことです。
import { redirect } from "next/navigation";
export default async function Page() {
// エラーが発生する可能性のある処理だけを try/catch で囲む
let user;
try {
user = await getUser();
} catch (error) {
console.error("ユーザー取得に失敗しました:", error);
return <div>エラーが発生しました</div>;
}
// redirect は try/catch の外で呼び出す
if (!user) {
redirect("/login");
}
return <div>ようこそ、{user.name} さん</div>;
}
解決方法 2: unstable_rethrow を使用する
どうしても try/catch ブロック内で redirect() や notFound() を呼び出す必要がある場合は、unstable_rethrow を使用できます。
unstable_rethrow は、Next.js 内部で使用される特別なエラー(リダイレクトや 404 など)を再度 throw するための関数です。catch ブロックでこの関数を呼び出すと、Next.js のエラーだけを上位に伝播させ、それ以外のエラーは通常通り処理できます。
使用例
import { redirect, unstable_rethrow } from "next/navigation";
export default async function Page() {
try {
const user = await getUser();
if (!user) {
redirect("/login");
}
return <div>ようこそ、{user.name} さん</div>;
} catch (error) {
// Next.js 内部のエラーは再度 throw する
unstable_rethrow(error);
// それ以外のエラーはここで処理する
console.error("エラーが発生しました:", error);
return <div>エラーが発生しました</div>;
}
}
unstable_rethrow は catch ブロックの最上部で呼び出してください。これにより、Next.js のエラーが誤って処理されることを防げます。
対象となるその他の API
unstable_rethrow は redirect() や notFound() だけでなく、以下の API が throw するエラーも再度 throw します。
cookies()headers()searchParamsfetch(..., { cache: 'no-store' })fetch(..., { next: { revalidate: 0 } })
これらの API は、ルートセグメントが静的であることを強制されている場合(dynamic = 'error' や Partial Prerendering が有効な場合など)にエラーを throw します。このエラーも開発者が catch するべきではないため、unstable_rethrow の対象となっています。
以下は、cookies() を使用する例です。
import { cookies } from "next/headers";
import { unstable_rethrow } from "next/navigation";
export default async function Page() {
try {
// cookies() は静的レンダリングが強制されている場合にエラーを throw する
const cookieStore = await cookies();
const token = cookieStore.get("token");
// 何らかの処理...
} catch (error) {
// cookies() が throw したエラーも unstable_rethrow で再度 throw される
unstable_rethrow(error);
console.error("エラーが発生しました:", error);
return <div>エラーが発生しました</div>;
}
}
If a route segment is marked to throw an error unless it's static, a Dynamic API call will also throw an error that should similarly not be caught by the developer.
注意点
関数名に unstable が付いていることからわかるように、この API は将来変更される可能性があります。可能であれば、解決方法 1(try/catch の外で呼び出す)を優先してください。
unstable_rethrow の設計や今後の方向性については、Next.js の GitHub Discussion で議論されています。
補足: 例外の型について
redirect() や notFound() が throw するエラーは、RedirectError のような専用クラスではなく、通常の Error クラスに型アサーションを行っているだけです。
const error = new Error(REDIRECT_ERROR_CODE) as RedirectError;
const error = new Error(DIGEST) as HTTPAccessFallbackError;
そのため、instanceof による型ガードでこれらのエラーを区別することはできません。
まとめ
この記事では、Next.js App Router の redirect()、notFound()、permanentRedirect() が例外を throw する仕組みについて解説しました。
要点の整理
- これらの関数は React のレンダリングを中断するために例外を throw する
- try/catch ブロック内で使用すると、エラーがキャッチされてしまい正しく動作しない
- 解決方法 1(推奨): try/catch の外で呼び出す
- 解決方法 2:
unstable_rethrowを使用して Next.js のエラーを再度 throw する