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!

4 Comments

  1. This guide seems like a great step-by-step tutorial for building a real-time chat app. I appreciate how it breaks down the process into manageable sections, especially for someone who might be new to WebSockets or React hooks. The emphasis on low latency and high performance is crucial for a chat app, so it’s good to see that highlighted. I’m curious, though, how scalable is this setup for a larger number of users? Also, are there any specific challenges you’ve encountered while implementing this, or tips to optimize it further? I’d love to hear your thoughts or any additional resources you’d recommend for diving deeper into real-time applications. What’s your experience with deploying this kind of app in production?

    • Emir

      Hi, and thank you so much for your thoughtful comment — and my sincere apologies for the late reply. Things have been incredibly busy on my end lately.
      You raise a great question about scalability. This setup works well for small to medium-sized applications, especially for prototyping or internal tools. However, for larger-scale production use, you’d definitely want to consider additional infrastructure like a message broker (e.g., Redis with socket.io-redis) to manage socket connections across multiple server instances. Load balancing and horizontal scaling become crucial as your user base grows.
      One challenge I encountered early on was managing socket disconnections cleanly and ensuring message delivery in less stable network environments. Heartbeat mechanisms and reconnection strategies are essential for improving reliability.
      As for deployment, Socket.io works well with platforms like Heroku (with WebSocket support enabled), or AWS using services like Elastic Beanstalk or ECS. I would also feel free to suggest looking into solutions like Ably or Pusher for managed real-time infrastructure if scaling is a priority.
      I really appreciate your interest, and I will definitely consider writing a follow-up post focused on scaling strategies and production deployment.

      Thanks again.

  2. This guide seems pretty straightforward for building a real-time chat app. I like how it emphasizes low latency and high performance—that’s crucial for a smooth user experience. The step-by-step instructions are clear, but I wonder if it could include more details about handling potential errors or scaling for larger user bases. Are there any specific best practices for optimizing WebSocket connections in production? Also, it would be helpful to see a demo or screenshots of the final app to visualize the result. Overall, it’s a solid guide, but I’m curious—what’s the most challenging part of building something like this?

    • Emir

      Hi, and thank you for your comment—and apologies for the delayed reply, I’ve been caught up in a very busy stretch lately.
      I really appreciate your feedback. And yes, you are so right—adding error handling and production-level optimization tips would make the guide more robust. In production, managing reconnections, detecting dropped connections, and setting reasonable timeouts are key best practices. Tools like socket.io’s built-in reconnection options or heartbeat pings can really help.
      As for the most challenging part: maintaining clean, predictable real-time behavior while scaling is often where things get tricky—especially across multiple server instances or in flaky network conditions.
      I will consider adding a follow-up with more focus on those aspects, along with visuals and perhaps a hosted demo link to help bring it all together.

      Thanks again for reading and for the great suggestions.

Leave a Reply

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