HomeBytesheetsRoadmapsUI TemplatesUI ToolsStack Discussion
6 Topics
Chapter•115 Minutes

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.

Route Error Boundary
'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>
  );
}
💡 The route can provide a recovery UI without exposing sensitive server details.
Key Takeaways
  • 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.

Resource Not Found
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>;
}
💡 The route explicitly distinguishes a missing resource from an unexpected application failure.
Key Takeaways
  • 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.

Domain-Oriented Architecture
Key Takeaways
  • 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.

Avoid Sensitive Logs

Production logs frequently have broader access than application databases. Treat logging as a data-security boundary.

Structured Logging
logger.info({
  event: 'product_created',
  requestId,
  userId,
  productId
});
💡 Structured fields make production logs easier to search and correlate.
Key Takeaways
  • 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.

Key Takeaways
  • 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.

Key Takeaways
  • Production configuration differs from local development.
  • Document required environment variables.
  • Test domain, cookie, caching, and authentication behavior in a production-like environment.