Next.js Is Sending My Console Logs Over WebSocket — And I Can't Turn It Off
I recently spent several hours tracking down a severe performance issue in a Next.js application.
At first, I suspected React re-renders, large state updates, WebSocket traffic, or even my own code. The profiler told a different story.
Most of the time was being spent inside:
node_modules/next/dist/next-devtools/userspace/app/forward-logs.js
More specifically:
sendClientFileLogs()
After digging deeper, I discovered that Next.js was intercepting browser console methods and forwarding log payloads to the development server.
The Surprise
Like many developers, I assume that:
console.log(data)
prints something to the browser console.
Apparently that's no longer true.
In development mode, Next.js wraps console methods with its own implementation:
const originalLog = console.log
console.log = (...args) => {
wrapper("log", ...args)
originalLog.apply(console, args)
}
The wrapper serializes the payload and sends it through a WebSocket connection back to the development server.
This might sound harmless until your application starts dealing with large objects.
The 65 MB Console Log
My application contains large runtime structures.
During profiling, I discovered that a single log statement was causing approximately 65 MB of data to be serialized and transmitted.
Not stored.
Not rendered.
Transmitted.
Over WebSocket.
Because I happened to log an object that contained a large state tree.
The result was dramatic CPU usage, massive allocations, and a development experience that became almost unusable.
The flamegraph clearly showed Next.js spending more time forwarding logs than my application spent doing actual work.
The Worse Part: Disabling It Doesn't Work
The obvious response is:
"Just disable the feature."
That was my first thought too.
I tried:
logging: false
I tried:
logging: {
browserToTerminal: false
}
I searched through the documentation.
I searched through GitHub issues.
I searched through source code.
The console was still wrapped.
The logs were still forwarded.
The WebSocket traffic was still happening.
As far as I can tell, there is currently no reliable, documented way to restore native browser console behavior.
The Workaround
The only thing that actually worked was bypassing Next.js entirely.
Yes, this is ugly.
Yes, this is a hack.
But it immediately removed the overhead:
setTimeout(() => {
if (typeof window !== 'undefined') {
// @ts-expect-error
global.console.log = global.console.log.__proto__
}
}, 0)
After applying this workaround, the forwarding stopped and performance returned to normal.
The fact that this hack is currently more reliable than the documented configuration options is concerning.
The Bigger Problem
The performance issue is annoying.
The architectural decision is what bothers me.
Overriding fundamental browser APIs is a very invasive thing for a framework to do.
If a framework intercepts:
- console.log
- console.warn
- console.error
and starts transmitting user data over the network, developers should have:
- Clear documentation.
- Explicit opt-in.
- A guaranteed opt-out.
Instead, many developers will only discover this behavior after profiling performance issues.
Frameworks Should Be Predictable
The real lesson here is not about logging.
It's about predictability.
When I write:
console.log(obj)
I expect a console log.
I do not expect:
- serialization
- network traffic
- WebSocket communication
- server-side forwarding
- additional CPU overhead
Modern frameworks keep adding convenience features, but every hidden layer of magic has a cost.
Sometimes that cost is measured in developer confusion.
Sometimes it's measured in milliseconds.
And sometimes it's measured in 65 MB WebSocket messages generated by a single console.log call.