Omid Sayfun
Omid SayfunComputer Geek
Home
Notebook
Journey

Online

Github
Linkedin
Hevy
Plyatomic
Notebook
The trap of making everything dynamic
March 01, 2024
A try at type-safe groupBy function in TypeScript
April 10, 2025
Email special headers
November 20, 2024
Adding prettier to eslint
April 10, 2025
Storing Vector in Postgres with Drizzle ORM
March 21, 2024
Upgrading my blog to Next 15
April 05, 2025
Canvas macOS issue
February 20, 2024
tsx doesn’t support decorators
March 26, 2025
Validating NestJS env vars with zod
February 06, 2025
Extending Window: types vs interfaces
March 21, 2025
Loading env file into Node process
February 06, 2025
Add update date field to Postgres
February 27, 2024
Using node API for delay
February 06, 2025
React Component Lifecycle
November 28, 2024
How CQRS is different than Event Sourcing
August 18, 2024
RabbitMQ exchange vs queue
August 14, 2024
PgVector similarity search distance functions
August 13, 2024
PgVector indexing options for vector similarity search
July 31, 2024
Using puppeteer executable for GSTS
June 08, 2024
Why EQ is Your Next Career Upgrade
May 13, 2024
Counting GPT tokens
June 30, 2024
Logging route handler responses in Next.js 14
June 19, 2024
Redirect www subdomain with Cloudflare
June 17, 2024
Logging requests in Express app
June 16, 2024
Move Docker volume to bind mount
June 12, 2024
Next.js Hydration Window Issue
May 29, 2024
Using Git rebase without creating chaos in your repo
May 16, 2024
Implementing RPC Calls with RabbitMQ in TypeScript
March 16, 2024
Optimize webpage load with special tags
March 15, 2024
What the hell is Open Graph?
March 13, 2024
My go-to Next.js ESlint config
March 10, 2024
List of useful Chrome args
March 10, 2024
Combining RxJS observables - Part 1
February 20, 2024

Logging route handler responses in Next.js 14

June 19, 2024

Switching from Express.js to Next.js for your server-side needs can be quite the transition, especially when it comes to maintaining the same level of request logging. In Express.js, tools like morgan or express-request-logger are commonly used to log requests, but Next.js requires a different approach. Here’s how you can achieve similar logging functionality in your Next.js application:

Creating a Logging Wrapper

Next.js does not support traditional middleware for intercepting responses. However, you can still log requests by wrapping your route handlers in a higher-order function. Let's create a function called withLogging to wrap around our route handlers. This function will log the request details and response body while ensuring that the response stream remains accessible and unchanged.

import { NextRequest, NextResponse } from "next/server";
 
const withLogging = (handler: (req: NextRequest) => Promise<NextResponse>) => {
	return async (req: NextRequest) => {
		// Call the handler and get the response
		const res = await handler(req);
 
		// Clone the response to avoid locking the stream


















Continue Reading

const
cloneResponse
=
res.
clone
();
// Extract and log the response body
const outputBody = await cloneResponse.text();
// Create log items
const logItems = [
req.method,
req.nextUrl.toString(),
res.status,
outputBody,
];
// Log the request and response details
console.log(logItems.join(" - "));
return res;
};
};

Next, we wrap our route handlers with the withLogging function. Here's an example of how to wrap a POST handler:

import { NextRequest, NextResponse } from "next/server";
 
export const POST = withLogging(async (req: NextRequest) => {
	return NextResponse.json({ message: "Hello, World!" });
});

When a POST request is made to this endpoint, the console will log something like this:

POST - http://localhost:3000/api - 200 - {"message":"Hello, World!"}

Why Clone the Response?

In Next.js, the body of the response is a ReadableStream. Once you read from the stream, it becomes locked, which can lead to errors if Next.js try to use it again. Cloning the response allows you to read the body without locking the original stream, ensuring the response can be returned untouched.

Here’s the relevant error you might encounter without cloning:

Error: failed to pipe response
 
[cause]: TypeError [ERR_INVALID_STATE]: Invalid state: The ReadableStream is locked
 

Cloning the response and then accessing its body ensures that the response remains in a valid state, especially for more complex responses like redirects or rewrites.

Performance Overhead

There is a slight performance overhead due to cloning the response and logging the data. However, this trade-off is generally acceptable given the benefits of improved visibility and easier debugging.

Environment-Based Logging

To control logging based on the environment, you can check process.env and conditionally apply the logging wrapper. This allows you to enable detailed logging in development while keeping production logs clean and efficient.

const withLogging = (handler: (req: NextRequest) => Promise<NextResponse>) => {
	return async (req: NextRequest) => {
		if (process.env.NODE_ENV === "development") {
			const res = await handler(req);
			const cloneResponse = res.clone();
 
			const outputBody = await cloneResponse.text();
 
			const logItems = [
				req.method,
				req.nextUrl.toString(),
				res.status,
				outputBody,
			];
 
			console.log(logItems.join(" - "));
 
			return res;
		} else {
			return handler(req);
		}
	};
};

This setup ensures that logging can be adjusted according to the environment, minimizing unnecessary overhead in production.

Customizing Logs

You can customize the logged items by adding more details to the logItems array. Be cautious to avoid logging sensitive information that could pose security risks or clutter the logs.

This approach provides a robust logging solution for Next.js applications, enabling better request tracking and response handling while maintaining application performance and security.

  • 02-20-2026

    The trap of making everything dynamic

  • 04-11-2025

    A try at type-safe groupBy function in TypeScript

  • 04-10-2025

    Email special headers

  • 04-10-2025

    Adding prettier to eslint

  • 04-09-2025

    Storing Vector in Postgres with Drizzle ORM