Lesson 37 of 81
A Server Component has three different ways to abandon its normal render, and they all work by throwing — but each throw means something different to Next.js, and lands on a different piece of UI.
11 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
redirect(), notFound(), and a plain throw new Error(...) all interrupt a Server Component's render the same way, mechanically: they throw. What happens next is where they diverge, because Next.js's runtime recognizes the first two as specific signals, and treats the third as a genuine failure:
redirect(path) throws NEXT_REDIRECT. Next.js sends the browser elsewhere — a real 307 (or 308 via permanentRedirect) on a direct navigation, or a client-side navigation during streaming, since headers can't be rewritten once a response has started.notFound() throws NEXT_HTTP_ERROR_FALLBACK;404. Next.js renders the nearest not-found.tsx and adds a noindex meta tag.Error is not a signal Next.js recognizes specially — it's an actual exception, caught by the nearest error.tsx error boundary (which must be a Client Component), showing the user a "something went wrong" UI with a retry button.Picking the wrong one doesn't crash anything — it just shows the user the wrong story about what happened.
// app/dashboard/[teamId]/page.tsx
import { redirect, notFound } from "next/navigation";
import { getSession } from "@/lib/auth";
import { getTeam } from "@/lib/teams";
export default async function TeamDashboard({
params,
}: {
params: Promise<{ teamId: string }>;
}) {
const { teamId } = await params;
const session = await getSession();
if (!session) {
redirect("/login"); // go somewhere else — the user just isn't signed in
}
const team = await getTeam(teamId);
if (!team) {
notFound(); // this specific resource genuinely doesn't exist
}
if (team.memberIds === undefined) {
throw new Error("Team record is missing memberIds — data integrity bug");
// something is actually broken — let error.tsx show a retry, not a friendly 404
}
return <h1>{team.name}</h1>;
}
Each branch answers a different question. "Are you allowed to be here at all?" is a redirect. "Does the thing you asked for exist?" is a not-found. "Did something on our end break in a way the user can't fix by navigating or refreshing their intent?" is a real error — and a real error is the only one of the three that should ever show a "try again" button, because it's the only one where trying again might actually work.
redirect()'s type parameter only matters in Server Actions. It defaults to 'replace' everywhere except Server Actions, where it defaults to 'push'; in a plain Server Component the type argument has no effect at all.redirect() can't send a real HTTP redirect either — same constraint as notFound()'s status code. Once a response has started streaming as a 200, Next.js falls back to a meta tag or client-side navigation instead of rewriting headers that already left the server.Error gets a genuinely different UI than a not-found, and that's the point. Using throw new Error() for "this resource doesn't exist" shows the user an error boundary with a retry button that will never succeed, instead of the calmer not-found page that's actually appropriate — the wrong exit isn't a bug, but it is a real UX mistake.Error is caught by error.tsx. redirect() and notFound() are intercepted earlier, by Next.js's own routing layer, before they'd ever reach a React error boundary — error.tsx never sees them and isn't meant to.This decision sits directly on top of the loading, error, and not-found UI conventions — error.tsx and not-found.tsx are the two possible landing spots for two of these three exits, and knowing which file will actually catch a given throw is what makes the choice concrete instead of abstract.