- Next.js
- App Router File Conventions
- Not-Found Handling: not-found.tsx and notFound()
Not-Found Handling: not-found.tsx and notFound()
not-found.tsx renders a custom 404 UI for unmatched URLs or when programmatically triggered via notFound(). When invoked during server rendering, Next.js emits an HTTP 404 status code header to search engine crawlers. Like error boundaries, not-found.tsx renders inside the parent layout.
Learning Objectives
Trigger semantic 404 responses programmatically using notFound().
Understand HTTP 404 status code emission during SSR vs client navigation.
Structure segment-level vs global root 404 pages.
Prevent soft 404 SEO penalties by returning proper HTTP response codes.
Prerequisites & Background Knowledge
Why Interviewers Care
High relevance for SEO compliance, crawler indexing, and robust dynamic entity fetching.
The Challenge
How does Next.js handle 404 states with not-found.tsx and the notFound() function? How does it prevent "soft 404" SEO penalties during server rendering?
Code Architecture & Implementation
1// app/products/[id]/page.tsx2import { notFound } from 'next/navigation';34export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {5 const { id } = await params;67 const product = await fetch(`https://api.store.com/products/${id}`, {8 cache: 'no-store'9 }).then(res => (res.ok ? res.json() : null));1011 // If item does not exist in DB, trigger notFound()12 if (!product) {13 notFound(); // Throws NEXT_NOT_FOUND error internally14 }1516 return (17 <div>18 <h1>{product.title}</h1>19 <p>{product.description}</p>20 </div>21 );22}2324// app/products/[id]/not-found.tsx25// Rendered inside products/[id] layout with HTTP 404 status header26export default function ProductNotFound() {27 return (28 <div className="not-found-card">29 <h2>Product Does Not Exist</h2>30 <p>The requested product has been discontinued or removed.</p>31 </div>32 );33}
Architectural Explanation
Mechanics of notFound()
When notFound() is invoked inside a Server Component, Route Handler, or Server Action:
- Control Flow Exception: Next.js throws an internal
NEXT_NOT_FOUNDerror that halts component execution. - HTTP 404 Emission: During initial SSR, Next.js sets the HTTP response header to
404 Not Found. This is critical for SEO: it prevents "soft 404s" (where missing pages return 200 OK, wasting search crawler budgets). - UI Replacement: The nearest parent
not-found.tsxfile is rendered in place of the page. - Layout Scoping: The surrounding layout (headers, footers) remains intact.
Segment-Level vs Global 404
app/not-found.tsx: Handles globally unmatched URLs (e.g./unknown-path).app/products/[id]/not-found.tsx: Handles missing dynamic entities specifically within that route segment.
Common Mistakes & Anti-Patterns
Wrapping notFound() in a generic catch (error) block without re-throwing, preventing Next.js from rendering the 404 UI.
Returning a custom <p>Not found</p> component without calling notFound(), causing a soft 404 with a 200 OK HTTP status code.
Expecting not-found.tsx to receive route parameters as props (it receives no props).
Interview Follow-Up Traps
Interviewer asks: "If a user navigates on the client via <Link> to a page that calls notFound(), does the browser make a new HTTP request?" Trap answer: Yes, to get the 404 status. Correct answer: No. During client navigation, Next.js receives the RSC payload indicating not-found and swaps the UI via client-side routing.
Interviewer asks: "What happens if you wrap notFound() in a try/catch (err) block?" Trap answer: It safely logs the 404. Correct answer: The catch block swallows the NEXT_NOT_FOUND exception and breaks the 404 redirection.
Frequently Asked Questions
Can not-found.tsx be an async Server Component?
Yes. not-found.tsx can be async and fetch recommendations or search links.
Can you invoke notFound() from a Route Handler?
Yes, calling notFound() in a Route Handler returns an HTTP 404 response.
Does not-found.tsx have access to the URL parameters?
No. not-found.tsx receives no props. To inspect params on the client, use the usePathname() hook.
How do you catch unmatched paths at the root of the app?
Create app/not-found.tsx at the root of the app directory.
