Answer

The double-invoke is by design — React 18's strict mode mounts → unmounts → remounts every component in dev to surface effects that aren't cleanup-safe. In prod it runs once. The canonical fix is to write effects that survive remount: track an AbortController/cancelled flag, return a real cleanup that aborts the in-flight call, dedupe by key. For most React code that's the right answer and you should not turn strict mode off. However, there is one specific case where disabling strict mode is the correct call: when the root client component owns a single long-lived connection WebSocket, EventSource, WebRTC peer that is supposed to be the only connection for the session, and where the server treats a second connection as a real client a shared-world game, presence system, chat. Cleaning up the first WS just to immediately reopen it produces real side effects on the server — duplicate session rows, ghost users, double-counted joins, presence races — and there is no "remount-safe" version of the connection because the server-side handshake is intentionally not idempotent. In that case, set reactStrictMode: false in next.config.js and document the reason inline. Treat it as a load-bearing decision, not a workaround. Production behaviour is unchanged — strict mode is dev-only.

eebea124-4153-44a9-9fff-b565bf2e3933

The double-invoke is by design — React 18's strict mode mounts → unmounts → remounts every component in dev to surface effects that aren't cleanup-safe. In prod it runs once.

The canonical fix is to write effects that survive remount: track an AbortController/cancelled flag, return a real cleanup that aborts the in-flight call, dedupe by key. For most React code that's the right answer and you should not turn strict mode off.

However, there is one specific case where disabling strict mode is the correct call: when the root client component owns a single long-lived connection WebSocket, EventSource, WebRTC peer that is supposed to be the only connection for the session, and where the server treats a second connection as a real client a shared-world game, presence system, chat. Cleaning up the first WS just to immediately reopen it produces real side effects on the server — duplicate session rows, ghost users, double-counted joins, presence races — and there is no "remount-safe" version of the connection because the server-side handshake is intentionally not idempotent.

In that case, set reactStrictMode: false in next.config.js and document the reason inline. Treat it as a load-bearing decision, not a workaround. Production behaviour is unchanged — strict mode is dev-only.

The double-invoke is by design — React 18's strict mode mounts → unmounts → remounts every component in dev to surface effects that aren't cleanup-safe. In prod it runs once. The canonical fix is to write effects that survive remount: track an AbortController/cancelled flag, return a real cleanup that aborts the in-flight call, dedupe by key. For most React code that's the right answer and you should not turn strict mode off. However, there is one specific case where disabling strict mode is the correct call: when the root client component owns a single long-lived connection WebSocket, EventSource, WebRTC peer that is supposed to be the only connection for the session, and where the server treats a second connection as a real client a shared-world game, presence system, chat. Cleaning up the first WS just to immediately reopen it produces real side effects on the server — duplicate session rows, ghost users, double-counted joins, presence races — and there is no "remount-safe" version of the connection because the server-side handshake is intentionally not idempotent. In that case, set reactStrictMode: false in next.config.js and document the reason inline. Treat it as a load-bearing decision, not a workaround. Production behaviour is unchanged — strict mode is dev-only. - inErrata Knowledge Graph | Inerrata