Advanced Architecture, Error Handling and Production Patterns
Design large Next.js applications with error boundaries, not-found handling, loading states, reusable services, observability, testing, and maintainable module boundaries.
Error Boundaries
Errors can happen during data fetching, rendering, or client interaction. A route-level error.tsx component can provide a recovery UI for errors within a route segment.
A good error experience should communicate what happened without exposing internal implementation details. It should also provide a recovery action where possible.
Not every error should be treated as a fatal application-wide failure.
'use client';
export default function Error({
reset
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<section>
<h2>Something went wrong.</h2>
<button onClick={() => reset()}>Try again</button>
</section>
);
}- Handle errors close to the UI boundary where recovery is possible.
- Do not expose stack traces or secrets to users.
- Provide recovery actions when retrying can succeed.
Not Found Pages
A missing resource is different from a server failure. Next.js provides a notFound mechanism for rendering an appropriate not-found boundary.
For example, if /products/123 does not exist, the application should produce a proper 404 experience rather than throwing an unrelated server error.
import { notFound } from 'next/navigation';
export default async function ProductPage({
params
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const product = await getProduct(id);
if (!product) {
notFound();
}
return <h1>{product.name}</h1>;
}- Use 404 semantics for missing resources.
- Do not treat every missing record as a 500 error.
- Create useful not-found experiences.
Domain-Oriented Project Architecture
As an application grows, organizing code purely by technical type can become difficult. Instead of one giant services directory, consider organizing important logic around business domains.
For example, an e-commerce application might contain features/orders, features/products, and features/payments, each with domain-specific components, services, validation, and types.
The goal is not to follow one universal folder structure. The goal is to create boundaries that allow teams to change one business capability without understanding the entire codebase.
- Organize large applications around meaningful boundaries.
- Keep business rules independent from UI where possible.
- Folder structure should reduce coupling rather than merely categorize files.
Logging, Monitoring and Observability
Production applications need more than error messages shown to users. Observability involves logs, metrics, traces, request identifiers, and meaningful error context.
A request ID can connect frontend-visible failures to backend logs. Structured logs are easier to search and analyze than arbitrary strings.
Never log passwords, access tokens, session secrets, payment data, or unnecessary personal information.
Production logs frequently have broader access than application databases. Treat logging as a data-security boundary.
logger.info({
event: 'product_created',
requestId,
userId,
productId
});- Use structured logs and correlation IDs.
- Monitor both errors and performance.
- Never log secrets or unnecessary sensitive data.
Testing Next.js Applications
A production Next.js application should use multiple levels of testing. Unit tests validate isolated business logic. Component tests validate UI behavior. Integration tests validate interactions between modules. End-to-end tests validate complete user workflows.
Do not test framework behavior that you do not own. Instead, test your application's business rules, authorization decisions, rendering outcomes, and important user journeys.
- Use different test levels for different risks.
- Prioritize critical business workflows.
- Test behavior rather than implementation details.
Production Configuration
Production configuration should be explicit and reproducible. Environment variables, database connections, logging configuration, authentication secrets, external service credentials, and build settings should be documented.
Do not rely on local development assumptions. Production may have different domains, HTTPS requirements, cookie behavior, caching, runtime constraints, and environment variables.
A deployment checklist should include authentication, error handling, monitoring, database migrations, rollback strategy, and cache behavior.
- Production configuration differs from local development.
- Document required environment variables.
- Test domain, cookie, caching, and authentication behavior in a production-like environment.