Search code examples
node.jssocketsnext.jssocket.iosocket.io-client

Using Socket.io with Next.js


Background

I'm trying to establish and connect a socket using Next.js. I'm following the standard guide that uses an API call to test if a socket server is already running and creates one if not.

I have an API script at pages/api/socket/io that looks like this:

import { Server } from "socket.io";

export default function SocketHandler(req, res) {

    if (res.socket.server.io) {
        console.log("Already set up");
        res.end();
        return;
    }

    const io = new Server(res.socket.server);

    // Event handler for client connections
    io.on('connection', (socket) => {
        const clientId = socket.id;
        console.log('A client connected');
        console.log(`A client connected. ID: ${clientId}`);
        io.emit('client-new', clientId);

        // Event handler for receiving messages from the client
        socket.on('message', (data) => {
            console.log('Received message:', data);
        });

        // Event handler for client disconnections
        socket.on('disconnect', () => {
            console.log('A client disconnected.');
        });
    });

    res.socket.server.io = io;
    res.end();
}

I have a page under pages/chat.js that pings /api/socket/io to make sure the socket server is running, then attempts to connect to it.

import React, { useEffect, useState } from "react";
import io from "socket.io-client";

let socket;

const Page = () => {
    const [message, setMessage] = useState("");
    const [username, setUsername] = useState("");

    useEffect(() => {
        socketInitializer();

        return () => {
            socket.disconnect();
        };
    }, []);

    async function socketInitializer() {

        // ping the server to setup a socket if not already running
        await fetch("/api/socket/io");

        // Setup the Socket 
        socket = io({ path: "/api/socket/ping" });

        // Standard socket management
        socket.on('connect', () => {
            console.log('Connected to the server');
        });

        socket.on('disconnect', () => {
            console.log('Disconnected from the server');
        });

        socket.on('connect_error', (error) => {
            console.log('Connection error:', error);
        });

        socket.on('reconnect', (attemptNumber) => {
            console.log('Reconnected to the server. Attempt:', attemptNumber);
        });

        socket.on('reconnect_error', (error) => {
            console.log('Reconnection error:', error);
        });

        socket.on('reconnect_failed', () => {
            console.log('Failed to reconnect to the server');
        });

        // Manage socket message events
        socket.on('client-new', (message) => {
            console.log("new client", message);
        });

        socket.on('message', (message) => {
            console.log("Message", message);
        });

        socket.on('client-count', (count) => {
            console.log("clientCount", count)
        });

    }

    function handleSubmit(e) {
        e.preventDefault();

        socket.emit("message", {
            username,
            message
        });

        setMessage("");
    }

    return (
        <div>
            <h1>Chat app</h1>
            <h1>Enter a username</h1>

            <input value={username} onChange={(e) => setUsername(e.target.value)} />

            <div>
                <form onSubmit={handleSubmit}>
                    <input
                        name="message"
                        placeholder="enter your message"
                        value={message}
                        onChange={(e) => setMessage(e.target.value)}
                        autoComplete={"off"}
                    />
                </form>
            </div>
        </div>
    );
};

export default Page;

/api/socket/ping is a simple script that doesn't do much beyond return ok status. I'm assuming it should check that the server is running and hold any logic to check the client is still valid in the future, but it shouldn't be preventing the client from connecting at this point.

It looks like this:

export default async function handler(req, res) {
    return res.status(200).send("OK");
}

Problem

The issue I'm having is that it seems the socket server is getting set up. However, I can't get the client to connect.

I'm getting an error that suggests I may need to provide some additional configuration options as part of the client setup socket = io({ path: "/api/socket/ping" });, but I'm not sure where to start.

I'm running the server on localhost port 3000. This is the error I'm getting from :

Connection error: Error: server error
    at Socket.onPacket (socket.js:318:1)
    at Emitter.emit (index.mjs:136:1)
    at Polling.onPacket (transport.js:97:1)
    at callback (polling.js:115:1)
    at Array.forEach (<anonymous>)
    at Polling.onData (polling.js:118:22)
    at Emitter.emit (index.mjs:136:1)
    at Request.onLoad (polling.js:361:1)
    at xhr.onreadystatechange (polling.js:297:1)

Request

Please explain why the client is not connecting and make recommendations to fix the issue.


Solution

  • import { Server } from "socket.io";
    import cors from "cors";
    import nextConnect from "next-connect";
    
    const handler = nextConnect();
    
    // Enable CORS
    handler.use(cors());
    
    handler.all((req, res) => {
      if (res.socket.server.io) {
        console.log("Already set up");
        res.end();
        return;
      }
    
      const io = new Server(res.socket.server);
    
      // Event handler for client connections
      io.on("connection", (socket) => {
        const clientId = socket.id;
        console.log("A client connected");
        console.log(`A client connected. ID: ${clientId}`);
        io.emit("client-new", clientId);
    
        // Event handler for receiving messages from the client
        socket.on("message", (data) => {
          console.log("Received message:", data);
        });
    
        // Event handler for client disconnections
        socket.on("disconnect", () => {
          console.log("A client disconnected.");
        });
      });
    
      res.socket.server.io = io;
      res.end();
    });
    
    export default handler;