loading.js and Automatic Suspense Boundaries
When you add a loading.tsx file next to a page.tsx in the App Router, Next.js automatically wraps that route segment's content in a React Suspense boundary and shows the loading.tsx component as the fallback while the page's Server Component is fetching data. This means you get instant loading UI, like a skeleton screen or spinner, without manually writing any Suspense JSX yourself, because the file-based convention wires it up for you. The loading state also enables streaming: the shared layout and any static parts of the page can be sent to the browser immediately while slower, dynamic content continues rendering on the server behind the scenes.
Cricket analogy: loading.tsx is like the 'players walking out' graphic broadcasters show while ground staff finish covering the pitch after rain, keeping viewers engaged instead of a blank screen during the delay.
error.js and Error Boundaries
An error.tsx file defines a React Error Boundary for its route segment, automatically catching runtime errors thrown during rendering, including errors from async Server Components, and displaying a fallback UI instead of crashing the whole app. Because error.tsx renders on the client and needs interactivity, it must start with the 'use client' directive, and Next.js passes it two props: error (the thrown Error object, possibly including a digest for server-logged errors) and reset, a function that attempts to re-render the segment from scratch. This lets you build a 'Try again' button that re-runs the failed data fetch without forcing the user to do a full page reload.
Cricket analogy: error.tsx's reset function is like a no-ball being called and the batter getting a free hit replay of that delivery, a clean retry instead of the whole innings being scrapped.
// app/dashboard/error.tsx
'use client';
import { useEffect } from 'react';
export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error('Dashboard render error:', error.digest ?? error.message);
}, [error]);
return (
<div role="alert">
<h2>Something went wrong loading your dashboard.</h2>
<button onClick={() => reset()}>Try again</button>
</div>
);
}Not Found Handling with not-found.js
A not-found.tsx file defines the UI shown when a route can't be matched, or when code explicitly calls the notFound() function imported from next/navigation, such as when a database lookup for a dynamic [slug] segment returns nothing. Calling notFound() throws a special internal signal that Next.js catches to render the nearest not-found.tsx boundary and respond with a 404 HTTP status code, distinguishing it from a thrown Error which would instead be caught by error.tsx. A global app/not-found.tsx acts as the catch-all fallback for any unmatched route in the entire application, while nested not-found.tsx files can provide more specific 404 pages for a particular route segment.
Cricket analogy: notFound() is like an umpire correctly ruling 'not out' with certainty on a clean bat-pad decision, a definite, expected outcome, distinct from a rain-delay error that halts play unexpectedly.
Granular Loading with Suspense
Beyond the file-based loading.tsx, you can wrap individual slow components in your own <Suspense fallback={...}> boundaries to stream in parts of a page independently, which is especially powerful when one section, like a personalized recommendations widget, is much slower than the rest of the page. Because React Server Components can be async, awaiting a slow fetch inside a component wrapped in Suspense lets the rest of the page render and reach the browser immediately, with only that component's fallback showing until its data resolves. This pattern, often combined with streaming, means a page's static header and product details can appear instantly while a slower reviews section streams in a moment later.
Cricket analogy: Granular Suspense boundaries are like a broadcast showing the main match feed live while a separate 'DRS review in progress' box loads independently in the corner, without freezing the whole broadcast.
Streaming with Suspense requires the server to support HTTP streaming responses. On most modern hosting platforms, including Vercel and any Node.js server running next start, this works out of the box; some edge or serverless environments with strict buffering can affect how progressively the stream is flushed to the client.
error.tsx cannot catch errors thrown in the layout.tsx that wraps it, because the error boundary is rendered inside that layout, not around it. To catch errors originating in a root layout, you need a special global-error.tsx file at the top of the app directory, which must also define its own <html> and <body> tags since it replaces the root layout entirely when active.
- loading.tsx automatically wraps its route segment in a Suspense boundary, showing instant fallback UI during data fetching.
- error.tsx defines a Client Component Error Boundary that catches rendering errors and receives error and reset props.
- The reset function attempts to re-render the failed segment without a full page reload.
- notFound() from next/navigation renders the nearest not-found.tsx and returns an HTTP 404 status.
- Nested not-found.tsx files can override the global app/not-found.tsx for specific route segments.
- Custom <Suspense> boundaries around individual async components enable granular, independent streaming.
- global-error.tsx is required to catch errors thrown in the root layout itself, and must define its own html and body tags.
Practice what you learned
1. What does adding a loading.tsx file next to page.tsx automatically do?
2. Which two props does error.tsx receive?
3. What is the effect of calling notFound() inside a Server Component?
4. Why can't error.tsx catch an error thrown inside its own parent layout.tsx?
5. What must global-error.tsx include that a normal error.tsx does not?
Was this page helpful?
You May Also Like
Dynamic Routes and Params
Understand how to build dynamic and catch-all routes using bracket folder naming, read route params in Server and Client Components, and pre-render them with generateStaticParams.
The Link Component and Navigation
Learn how Next.js's Link component powers fast, client-side navigation with automatic prefetching, and how to complement it with the useRouter and usePathname hooks.
Middleware in Next.js
Learn how middleware.ts intercepts requests on the Edge Runtime before they reach a route, and how to use it for redirects, rewrites, and authentication checks.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics