afaqrashid.com  ·  portfolio · 2026 ed.

"Error: NEXT_REDIRECT" in Next.js: Why redirect() Isn't Working, Tested

redirect() from next/navigation doesn't return. It throws a special error called NEXT_REDIRECT, and Next catches it higher up and turns it into the redirect. If your own try/catch catches it first, the redirect never happens, and the only sign is "NEXT_REDIRECT" in your logs.

I tested redirect() in Server Actions, pages, a Route Handler and a client component on Next.js 16.4.4 with a production build.

The usual cause: redirect() inside try

"use server";
import { redirect } from "next/navigation";

export async function save() {
  try {
    await db.insert(/* ... */);
    redirect("/done"); // throws, and the catch below swallows it
  } catch (e) {
    console.log("CAUGHT", (e as Error).message, (e as { digest?: string }).digest);
  }
}
CAUGHT NEXT_REDIRECT NEXT_REDIRECT;push;/done;307;

The button press did nothing: the browser stayed on the form page. The digest shows what the error carries: the method, the target and the status code. Next would have used it if the error had reached it.

A page does the same thing. redirect() inside try/catch in a server component returned 200 and rendered the rest of the page, with "NEXT_REDIRECT" logged by the catch. Without try/catch the same page returned 307 with the location header.

Fix 1: redirect after the try/catch

export async function save() {
  try {
    await db.insert(/* ... */);
  } catch (e) {
    return { error: "Couldn't save" };
  }
  redirect("/done");
}

Redirected to /done. This is the cleanest fix: only the code that can really fail goes in try, and the redirect runs once it has succeeded. Return from the catch so a failed save doesn't fall through to the redirect.

Fix 2: unstable_rethrow at the top of catch

import { redirect, unstable_rethrow } from "next/navigation";

export async function save() {
  try {
    await db.insert(/* ... */);
    redirect("/done");
  } catch (e) {
    unstable_rethrow(e); // rethrows NEXT_REDIRECT, notFound() and other Next errors
    console.error(e);   // only your own errors get here
  }
}

Redirected to /done. Use this when the redirect has to stay inside the try, for example in a helper deep in your code. It has to be the first line of the catch, before anything that logs or reports the error. It also passes through notFound() and the dynamic-rendering signals that try/catch can swallow in the same way.

Fix 3: try/finally is fine

redirect() inside try with only a finally block (no catch) redirected correctly, and the finally block still ran. If you only need cleanup, you don't need a catch at all.

What else I measured

where                                  result
redirect() in a page                   307, location header
permanentRedirect() in a page          308, location header
redirect() in a Route Handler          307, location header
redirect() to an external URL (action) browser landed on the external site
redirect() in a client onClick         navigated, but logged "Uncaught Error: NEXT_REDIRECT"
router.push() in a client onClick      navigated, no error

The onClick case is the other place people see the raw error. redirect() is for rendering and Server Actions. In an event handler it still navigated in my test, but as an uncaught error, so error trackers like Sentry would report it every time. Use router.push() from useRouter for client navigation.

The status codes matter for SEO: redirect() gives a temporary 307, so for a page that has moved for good use permanentRedirect() (308). There's more on that in the redirects post linked below.

Quick check

  • Seeing NEXT_REDIRECT in logs or Sentry? Something caught it. Search for try/catch around the code that calls redirect(), including in helpers it calls.
  • Promise .catch() chains catch it too, the same as try/catch.
  • In a client component, use router.push() or <Link>, not redirect().
  • In proxy (middleware), use NextResponse.redirect(), not redirect().

All results above come from one Next.js 16.4.4 project built with next build and run with next start on Node 26. The browser checks were in Chrome.

Related

Sources