☠️ ☠️ DO NOT FOLLOW THIS ☠️ ☠️git
We are keeping this page just for archive, do not follow this patter if you can avoid it. It makes the next app slow, harder to debug and break HMR
Sockets in Next.js
Some activations requires multiple browsers to be synced, the following is a step by step on how to get your sockets in Next.js running.
INFO
The following pattern ensures there is only one connection per browser tab, and that the connection is reused across components.
Setting up the server
Install the required dependencies
npm i socket.io socket.io-client ts-node
Create a new file under ./src/server/index.ts
and paste the following code. This file will be used to create a socket server that listens for incoming connections and emits events to connected clients. It will replace the default Next.js server, wrapping it in our own server that has sockets.
import next from "next";
import { createServer } from "http";
import { Server } from "socket.io";
import { SOCKET_EVENTS } from "../lib/const";
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const PORT = Number(process.env.NEXT_PUBLIC_PORT || process.env.PORT) || 3000;
const server = createServer(async (req, res) => {
try {
return await handle(req, res);
} catch (err) {
console.error("Error occurred handling", req.url, err);
res.statusCode = 500;
res.end("internal server error");
}
})
.once("error", (err) => {
console.error(err);
process.exit(1);
})
.listen(PORT, () => {
if (process.send) process.send("ready");
console.log(`\x1b[36m%s\x1b[0m`, `> Ready on http://localhost:${PORT}`);
});
const io = new Server(server);
io.on("connection", (socket) => {
socket.on(SOCKET_EVENTS.JOIN_ROOM, (room: string) => {
console.log("Joining room: ", room);
socket.join(room);
});
socket.on(SOCKET_EVENTS.LEAVE_ROOM, (room: string) => {
console.log("leaving room: ", room);
socket.leave(room);
});
// start adding your custom events `NAVIGATE_TO` is just an example
socket.on(
SOCKET_EVENTS.NAVIGATE_TO,
({ room, path }: { room: string; path: string }) => {
socket.to(room).emit(SOCKET_EVENTS.NAVIGATE_TO, path);
}
);
});
});
Update your package.json
scripts to include the following:
"dev": "TURBOPACK=1 NODE_ENV=development ts-node --project tsconfig.server.json src/server/index.ts",
"build": "rm -rf dist && tsc --project tsconfig.server.json && next build",
"start": "NODE_ENV=production NEXT_PUBLIC_PORT=$npm_config_port node dist/server/index.js",
Create a ./tsconfig.server.json
file to compile the new server and add the following content
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "dist",
"target": "es2017",
"isolatedModules": false,
"noEmit": false
},
"include": ["src/server/**/*.ts"]
}
Important
Change the moduleResolution
to node
in the main ./tsconfig.json
file.
Remember
Adjust your PM2 configuration in ecosystem.config.js
to correctly point to the built server file.
See more: Deploying to NUCs
Socket Components and hooks for react.
You can structure your folders as you prefer. Below is an example setup for socket-related components and hooks. If possible use the plop generator to create the files.
- Socket Initialization: Create a file at ./src/components/SocketConnect/socket.ts:
"use client";
import { io } from "socket.io-client";
export const socket = io({
reconnection: true,
reconnectionDelay: 500,
reconnectionAttempts: 10,
});
- Socket Context and Hooks: Create a file at ./src/components/SocketConnect/SocketConnect.ts:
"use client";
import { SOCKET_EVENTS } from "@/lib/const";
import { useParams } from "next/navigation";
import {
ReactNode,
createContext,
useContext,
useEffect,
useState,
} from "react";
import { Socket } from "socket.io-client";
import { socket as newSocket } from "./socket";
// Create a context with a default value of null for the socket
const SocketContext = createContext<Socket | null>(null);
export const useSocket = () => useContext(SocketContext);
/**
* A custom hook that allows components to listen for socket events and execute a callback function when the event occurs.
* @param eventName - The name of the socket event to listen for.
* @param callback - The function to be executed when the socket event occurs. It takes one argument, `arg1`, which represents the data received from the event.
*/
export function useSocketOn<Arg1>(
eventName: keyof typeof SOCKET_EVENTS,
callback: (arg1: Arg1) => void
) {
const socket = useSocket();
useEffect(() => {
if (socket) {
socket.on(eventName, callback);
}
return () => {
if (socket) {
socket.off(eventName, callback);
}
};
}, [socket, eventName, callback]);
}
export const useSocketEmit = () => {
const socket = useSocket();
const cb = (eventName: keyof typeof SOCKET_EVENTS, data: any) => {
``;
if (socket) {
socket.emit(eventName, data);
}
};
return cb;
};
export const SocketProvider = ({ children }: { children: ReactNode }) => {
const { room } = useParams();
const [connected, setConnected] = useState(false);
const [socket, setSocket] = useState<Socket | null>(newSocket);
useEffect(() => {
const onConnect = () => {
if (room) {
newSocket.emit(SOCKET_EVENTS.JOIN_ROOM, room);
} else {
console.warn("No room provided. Cannot join room.");
}
setConnected(true);
};
const onDisconnect = () => {
console.log("disconnecting socket");
};
if (newSocket.connected) {
onConnect();
}
newSocket.on("connect", onConnect);
newSocket.on("disconnect", onDisconnect);
if (!connected) {
newSocket.connect();
}
return () => {
newSocket.off("connect", onConnect);
newSocket.off("disconnect", onDisconnect);
};
}, [connected, room]);
return (
<SocketContext.Provider value={socket}>{children}</SocketContext.Provider>
);
};
Implementing the SocketProvider
Wrap your template.tsx
with the SocketProvider
component
import SocketProvider from "@/components/atoms/SocketConnect";
export default function Template({ children }: { children: React.ReactNode }) {
return (
<SocketProvider>
<main>{children}</main>
</SocketProvider>
);
}
And now from within your components, you can use the useSocketOn
and useSocketEmit
hooks to listen for and emit socket events.
import {
useSocketOn,
useSocketEmit,
} from "@/components/SocketConnect/SocketConnect";
import { SOCKET_EVENTS } from "@/lib/const";
export default function MyComponent() {
const emit = useSocketEmit();
const onEvent = (data: any) => {
console.log("Received data: ", data);
};
useSocketOn(SOCKET_EVENTS.MY_EVENT, onEvent);
return (
<button onClick={() => emit(SOCKET_EVENTS.MY_EVENT, { data: "Hello" })}>
Emit Event
</button>
);
}
This setup provides a robust foundation for managing real-time communication in your Next.js applications using sockets.