Building a Real-Time Chat App with Next.js, WebSockets, and Server-Side Rendering

Published: 28 October 2024

Introduction

Building real-time applications like a chat app requires a strong foundation in both frontend and backend technologies. In this tutorial, we will be constructing a real-time chat app using Next.js, WebSockets, and Server-Side Rendering (SSR). The goal is to leverage SSR for fast initial page loads while using WebSockets for seamless real-time communication between clients.

By the end of this guide, you will have a fully functional real-time chat app that works smoothly across devices, providing a great user experience with low latency and high performance.


Prerequisites

Before we dive into the code, here’s what you should be familiar with:

  • JavaScript and React (since Next.js is built on React)
  • Basic knowledge of Next.js and its routing system
  • WebSockets and their use for real-time communication
  • Experience with server-side rendering (SSR) and how it works in Next.js

You should also have the following installed:

  • Node.js (at least version 14.x)
  • A text editor (like VS Code)
  • npm or yarn

If you don’t have these set up already, head over to the official documentation to install them.


Step 1: Setting Up the Next.js Project

Let’s begin by creating a new Next.js application. In your terminal, run the following command:

npx create-next-app@latest real-time-chat

Once the app is created, navigate into the project directory:

cd real-time-chat

Now, install the required dependencies for WebSockets:

npm install socket.io-client socket.io
  • socket.io-client will be used in the frontend to establish the WebSocket connection.
  • socket.io will be used on the server to manage WebSocket connections.

Step 2: Creating the WebSocket Server

To enable real-time communication, we’ll need to set up a WebSocket server. Next.js supports custom servers, so we’ll use Socket.io to handle WebSocket connections within a custom server.

  1. In the root of your project, create a new file named server.js.
touch server.js
  1. Inside server.js, set up the custom server with Socket.io.
const express = require('express');
const next = require('next');
const http = require('http');
const socketIo = require('socket.io');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();
  const httpServer = http.createServer(server);
  const io = socketIo(httpServer);

  // WebSocket events
  io.on('connection', (socket) => {
    console.log('A user connected');

    // Listen for messages from clients
    socket.on('sendMessage', (msg) => {
      io.emit('receiveMessage', msg); // Broadcast message to all clients
    });

    socket.on('disconnect', () => {
      console.log('A user disconnected');
    });
  });

  // Next.js request handler
  server.all('*', (req, res) => {
    return handle(req, res);
  });

  httpServer.listen(3001, (err) => {
    if (err) throw err;
    console.log('> Ready on http://localhost:3001');
  });
});
  1. Add the server.js script to the package.json under scripts:
"scripts": {
  "dev": "node server.js",
  "build": "next build",
  "start": "next start"
}

With this setup, we have a Socket.io WebSocket server running on port 3001, which will listen for messages and broadcast them to connected clients.


Step 3: Creating the Real-Time Chat Frontend

Now, let’s build the frontend part of the chat application. We’ll use React and WebSockets to handle sending and receiving messages.

1. Setting Up WebSocket Client

First, we need to create a WebSocket client using the socket.io-client package. In the pages directory, create a new file called chat.js.

touch pages/chat.js

In this file, set up the WebSocket connection and manage messages with React hooks.

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

let socket;

const Chat = () => {
  const [messages, setMessages] = useState([]);
  const [newMessage, setNewMessage] = useState('');

  // Connect to WebSocket on mount
  useEffect(() => {
    socket = io('http://localhost:3001');

    // Listen for incoming messages
    socket.on('receiveMessage', (msg) => {
      setMessages((prevMessages) => [...prevMessages, msg]);
    });

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

  const handleSendMessage = () => {
    if (newMessage.trim()) {
      // Emit the new message to the server
      socket.emit('sendMessage', newMessage);
      setNewMessage(''); // Clear the input
    }
  };

  return (
    <div style={{ maxWidth: 600, margin: 'auto', padding: 20 }}>
      <h1>Real-Time Chat</h1>
      <div style={{ marginBottom: 20, maxHeight: 300, overflowY: 'auto' }}>
        {messages.map((message, index) => (
          <div key={index} style={{ padding: 10, borderBottom: '1px solid #ddd' }}>
            {message}
          </div>
        ))}
      </div>

      <input
        type="text"
        value={newMessage}
        onChange={(e) => setNewMessage(e.target.value)}
        placeholder="Type a message"
        style={{ padding: 10, width: '80%' }}
      />
      <button onClick={handleSendMessage} style={{ padding: 10, marginLeft: 10 }}>
        Send
      </button>
    </div>
  );
};

export default Chat;

Explanation:

  • The useEffect hook connects to the WebSocket server when the component mounts and sets up a listener for incoming messages using socket.on('receiveMessage').
  • socket.emit('sendMessage', message) sends the typed message to the server.
  • setMessages updates the state to render the received messages on the page.

Step 4: Server-Side Rendering (SSR) with Next.js

Now, let’s focus on the Server-Side Rendering (SSR) feature of Next.js. In this case, we will pre-render the chat page to improve performance and SEO. While WebSockets don’t rely on SSR for real-time messaging, SSR can still be beneficial for delivering the chat app’s initial HTML quickly.

Modify pages/chat.js to support SSR by exporting an async function, getServerSideProps, which will provide server-side data (if needed) to the page.

export async function getServerSideProps() {
  // You could fetch some data here, if needed
  // For instance, a list of users, or previous messages stored in a database
  return {
    props: {}, // This could be any data you want to pass to the page
  };
}

Step 5: Running the App

Now that we’ve set up both the backend and frontend, let’s run the application.

  1. First, start the server:
npm run dev
  1. Open http://localhost:3001/chat in your browser to see the chat app in action. You can open the app in multiple tabs to simulate real-time communication between users.

Conclusion

You’ve just built a real-time chat app with Next.js, WebSockets, and Server-Side Rendering. In this tutorial, we covered:

  • Setting up a custom WebSocket server with Socket.io.
  • Building a React-based chat interface to send and receive messages.
  • Integrating SSR in Next.js to improve performance for the initial load.

This is just the beginning. You can take this app further by adding features like user authentication, message history, and push notifications. You can also explore deploying your app using Vercel for SSR and Socket.io on platforms like Heroku or AWS for production.

By combining real-time communication with server-side rendering, you create an application that is both performant and interactive. Happy coding!

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *