Saturday, July 19, 2025

Access logs in Next.js production build

I frequently encounter architectural decisions that seem counterintuitive from an operational perspective. Recently, while containerizing a Next.js application, I discovered one such puzzling design choice that required a creative engineering solution.

The Problem: Silent Production Builds

During the Docker image creation process for our Next.js application, I encountered an unexpected operational blind spot: Next.js production builds generate zero access logs. This absence of fundamental observability data immediately raised concerns about our ability to monitor application behavior in production environments.

The conventional wisdom suggests deploying an nginx reverse proxy to capture access logs. However, as an architect focused on operational efficiency, introducing an additional process layer solely for logging felt architecturally unsound, particularly within containerized environments where process minimalism is a core principle.

Exploring Conventional Solutions

My initial investigation led me to application-level logging libraries such as winston and pino. While these tools excel at application logging, they operate within the application boundary and don't provide the standardized access log format that operations teams expect from web applications.

Root Cause Analysis

After extensive research into similar reported issues, I discovered the underlying cause: Vercel has intentionally omitted access logging from Next.js production builds. This architectural decision, while perhaps suitable for Vercel's managed platform, creates operational challenges for self-hosted deployments.

Deep Dive

Taking a source-code-first approach, I downloaded the Next.js repository and traced the request handling flow to its core: the async function requestListener(req, res) function. By strategically placing console.log statements within the node_modules Next.js installation, I successfully exposed the access log data we needed.

However, this manual modification approach presented obvious maintainability challenges for automated deployment pipelines.

Production-Ready Implementation

While researching sustainable patching methodologies, I discovered an excellent resource by TomUps (https://www.tomups.com/posts/log-nextjs-request-response-as-json/) that introduced patch-package, a tool designed precisely for this type of systematic source modification.

Their approach provided the foundational technique, though it captured extensive request/response metadata including headers and body content. For our operational requirements, I needed a more focused solution that provided essential access log fields: timestamp, URL, and HTTP status code.

Architectural Solution

The final implementation leverages patch-package combined with pino-http-print to deliver clean, standardized access logs that integrate seamlessly with our existing observability stack. This approach:

  • container efficiency by avoiding additional processes
  • Provides operational visibility through standard access log formats
  • Ensures deployment consistency via automated patching during image builds
  • Preserves maintainability through version-controlled patch files

Key Takeaway

This experience reinforces a fundamental architectural principle: when platform decisions conflict with operational requirements, creative engineering solutions can bridge the gap while maintaining system integrity. The key is balancing pragmatic problem-solving with long-term maintainability, exactly what patch-package enables in this scenario.

Steps

You can follow the steps in TomUps post and use the following patch.

No comments:

Reusing the same Next.js Docker image with runtime CDN assetPrefix

Recently, while investigating a production issue, I needed to deploy the production Docker image locally for debugging purposes. However, I ...