I'm trying to get a basic WebSocket setup to work:
Here's the most important files:
My Express server only has a single file that looks like this:
index.ts
import * as socket from "socket.io";
import express from "express";
import http from "http";
import cors from "cors";
const app = express();
app.use(cors());
const server = http.createServer(app);
const io = new socket.Server(server, {
cors: {
origin: true,
},
});
io.on("connection", async (socket) => {
console.log(`Connection established`, socket.id);
socket.on("disconnect", () => {
console.log("user disconnected", socket.id);
});
socket.on("chat-message", (text) => {
console.log(text);
socket.broadcast.emit("chat-message", text);
});
});
server.listen(4000, () => {
console.log("Server running on http://localhost:4000");
});
On the client, I have two relevant files:
app/lib/socket.ts
Here's the problem: This code gets executed twice when the app is first started! I see the initialize socket
log on the server (i.e. in my Terminal where I run the Next.js app) and on the client (i.e. in the Browser console).
import { io } from 'socket.io-client';
const url = 'http://localhost:4000';
console.log(`initialize socket`);
export const socket = io(url);
app/page.tsx
"use client";
import { useState, useEffect } from "react";
import { socket } from "./lib/socket";
interface Message {
text: string;
}
export default function Home() {
const [message, setMessage] = useState("");
const [messageHistory, setMessageHistory] = useState<Message[]>([
{
text: "hello",
},
{
text: "world",
},
]);
const newMessageReceived = (e) => {
console.log(`received message: `, e);
setMessageHistory((oldMessageHistory) => [...oldMessageHistory, { text: e }]);
};
useEffect(() => {
socket.on("chat-message", newMessageReceived);
return () => {
console.log(`return from useEffect`);
socket.off("chat-message", newMessageReceived);
};
}, []);
const sendMessage = async (e: any) => {
e.preventDefault();
const newMessage = message;
setMessage("");
console.log(`sendMessage`, newMessage);
socket.emit("chat-message", newMessage);
setMessageHistory((oldMessageHistory) => [...oldMessageHistory, { text: newMessage }]);
};
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
<code className="font-mono font-bold">CoolChat 😎</code>
</p>
</div>
<div className="">
{messageHistory.map((message, i) => {
return <div key={i}>{message.text}</div>;
})}
</div>
<form
id="text-input-container"
className="bg-gray-300 py-4 px-2 w-full flex items-center justify-center"
onSubmit={sendMessage}
>
<div className="text-center bg-white w-full md:w-1/3 px-3 py-2 flex gap-3 rounded-xl drop-shadow-2xl">
<input
name="message"
value={message}
onChange={(e) => setMessage(e.target.value)}
id="message"
className="focus:outline-none px-2 flex-1 rounded-xl"
type="text"
placeholder="What do you want to say?"
/>
<button type="submit" className="rounded-xl px-3 py-2 bg-gray-600 text-gray-100 text-sm">
Send
</button>
</div>
</form>
</main>
);
}
Here's what happens when I run the code:
npm run dev
Server running on http://localhost:4000
npm run dev
localhost:3000
initialize socket
initialize socket
Connection established wQGEI_1USi_4T9n-AAAB
Connection established 1SPz0765uUEvMQgBAAAD
Connection established BOAeHMUMeJ5zb2EJAAAF
localhost:3000
initialize socket
Connection established um8qdP-yV9btkqVAAAAH
localhost:3000
initialize socket
Connection established um8qdP-yV9btkqVAAAAH
So, as you see, the first time I connect to localhost:3000
, the socket gets initialized twice — once the server and once the client.
All subsequent times, with new browser windows navigating to localhost:3000
, the socket only initializes once (in the browser, not at all on the server) as I would expect.
As you mentioned you are using Next.js 14 and facing this issue in your local environment. It's likely that React is rendering your component twice which leads to multiple connections being established.
I would recommend you to disable reactStrictMode
in the next.config.js
file. It should fix the issue for you.
// next.config.js
module.exports = {
reactStrictMode: false,
}