Building a Real-Time Chat Application with Next.js 2026 and WebSockets
What You'll Build
In this tutorial, we will create a real-time chat application using Next.js 2026 and WebSockets. The final application will allow multiple users to connect and exchange messages instantly. Here’s a quick look at what the chat app will look like:
- Real-time messaging: Users can send and receive messages without refreshing the page.
- User-friendly interface: A simple UI to display messages and a text input for sending new messages.
- Scalable backend: A server that can handle multiple concurrent WebSocket connections.
Why This Matters
Real-time applications are increasingly important in today's digital landscape. Whether it's for chat, notifications, or live updates, the ability to push data to clients instantly is a critical feature for many modern applications.
- Problem: Traditional HTTP requests are not suitable for real-time communication because they require the client to repeatedly poll the server for updates.
- When to use it: Real-time chat applications, live sports scores, stock tickers, collaborative tools, etc.
- Who benefits: Developers building interactive applications, users who need instant feedback and updates.
Architecture Overview
Our real-time chat application will follow a client-server architecture. Below is a simple diagram of the architecture:
+----------------------+ +----------------------+
| | | |
| Next.js Client | <-------> | WebSocket Server |
| | | |
+----------------------+ +----------------------+
- Next.js Client: The frontend application built with Next.js 2026 that users interact with.
- WebSocket Server: A Node.js server that manages WebSocket connections and broadcasts messages to all clients.
Step-by-Step Implementation
Let's start building our chat application. We will go through the following steps:
- Set up a new Next.js project.
- Create a basic WebSocket server.
- Connect the Next.js client to the WebSocket server.
1. Set up a New Next.js Project
First, we'll create a new Next.js project. Open your terminal and run the following command:
npx create-next-app@latest real-time-chat
Navigate to the project directory:
cd real-time-chat
This command sets up a new Next.js 2026 application with the default configuration. You should see a basic Next.js structure with pages, public, and styles directories. This will serve as the foundation for our chat application.
2. Create a Basic WebSocket Server
Next, we'll set up a simple WebSocket server using Node.js. In the root of your project, create a new file named server.js:
// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
console.log('New client connected');
ws.on('message', (message) => {
console.log(`Received message: ${message}`);
// Broadcast the message to all clients
wss.clients.forEach((client) => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
console.log('WebSocket server is running on ws://localhost:8080');
Explanation:
- We use the
wslibrary to create a WebSocket server listening on port 8080. - When a new client connects, we log a message.
- When a message is received, it's logged and broadcasted to all other connected clients.
- We also log when a client disconnects.
To start the server, run:
node server.js
3. Connect the Next.js Client to the WebSocket Server
Now, we'll connect our Next.js client to the WebSocket server. Open the pages/index.js file and replace its content with the following code:
// pages/index.js
import { useEffect, useState } from 'react';
export default function Home() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
let socket;
useEffect(() => {
// Connect to the WebSocket server
socket = new WebSocket('ws://localhost:8080');
socket.onmessage = (event) => {
setMessages((prevMessages) => [...prevMessages, event.data]);
};
return () => {
socket.close();
};
}, []);
const sendMessage = () => {
if (input) {
socket.send(input);
setInput('');
}
};
return (
<div>
<h1>Real-Time Chat</h1>
<div>
{messages.map((msg, index) => (
<div key={index}>{msg}</div>
))}
</div>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message..."
/>
<button onClick={sendMessage}>Send</button>
</div>
);
}
Explanation:
- We use the
useStatehook to manage the chat messages and input field state. - The
useEffecthook establishes a WebSocket connection when the component mounts and sets up anonmessagehandler to update the messages. - The
sendMessagefunction sends the input value to the WebSocket server and clears the input field.
With this setup, your Next.js client should now connect to the WebSocket server and be able to send and receive messages in real-time. In the next steps, we'll improve the UI and add more features.
4. Improve the User Interface
Now that we have a basic chat application running, let's improve the user interface to make it more user-friendly. We'll add some basic styling to make the chat application more visually appealing. Update the styles/globals.css file with the following CSS:
/* styles/globals.css */
body {
font-family: Arial, sans-serif;
background-color: #f4f4f9;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.chat-container {
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 400px;
max-height: 500px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.messages {
flex: 1;
overflow-y: auto;
padding: 10px;
border-bottom: 1px solid #ddd;
}
.message {
margin-bottom: 10px;
padding: 8px;
background-color: #e1f5fe;
border-radius: 5px;
}
.input-container {
display: flex;
padding: 10px;
}
input[type="text"] {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 5px;
margin-right: 10px;
}
button {
padding: 8px 12px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
Update the pages/index.js to use these styles:
// pages/index.js
import { useEffect, useState } from 'react';
import styles from '../styles/globals.css';
export default function Home() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
let socket;
useEffect(() => {
socket = new WebSocket('ws://localhost:8080');
socket.onmessage = (event) => {
setMessages((prevMessages) => [...prevMessages, event.data]);
};
return () => {
socket.close();
};
}, []);
const sendMessage = () => {
if (input) {
socket.send(input);
setInput('');
}
};
return (
<div className="chat-container">
<h1>Real-Time Chat</h1>
<div className="messages">
{messages.map((msg, index) => (
<div key={index} className="message">{msg}</div>
))}
</div>
<div className="input-container">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message..."
/>
<button onClick={sendMessage}>Send</button>
</div>
</div>
);
}
5. Handle Edge Cases
To make the application more robust, let's handle some edge cases:
- Reconnect on Disconnect: Automatically attempt to reconnect if the WebSocket connection is lost.
- Error Handling: Display an error message if the connection fails.
Modify the useEffect in pages/index.js:
useEffect(() => {
const connect = () => {
socket = new WebSocket('ws://localhost:8080');
socket.onopen = () => {
console.log('Connected to WebSocket server');
};
socket.onmessage = (event) => {
setMessages((prevMessages) => [...prevMessages, event.data]);
};
socket.onclose = () => {
console.log('Disconnected. Attempting to reconnect...');
setTimeout(connect, 1000);
};
socket.onerror = (error) => {
console.error('WebSocket error: ', error);
};
};
connect();
return () => {
socket.close();
};
}, []);
Common Mistakes
- Incorrect WebSocket URL: Ensure the WebSocket URL matches the server's address and port.
- State Management Issues: Watch for stale state updates, especially when using hooks.
- Not Handling Reconnects: Not implementing reconnection logic can lead to a poor user experience when network issues occur.
How I Would Use This
- When to Use: Ideal for applications requiring real-time communication, such as chat apps, collaborative tools, or live notifications.
- When to Avoid: If your application does not require real-time updates, consider simpler alternatives like REST APIs.
- Production Considerations: Use a managed WebSocket service or a scalable solution like AWS API Gateway for production. Monitor connection counts and error rates.
- Cost and Maintenance: Consider the cost of maintaining WebSocket connections, especially with large user bases. Load balancing and scaling are critical.
Lessons Learned
- Tradeoffs: WebSockets provide real-time capabilities but come with increased complexity compared to REST APIs.
- Unexpected Issues: Network instability can cause frequent disconnects. Implementing robust reconnection logic is essential.
- Real-World Considerations: Consider security implications, such as using WSS (WebSocket Secure) in production environments.
Next Steps
- Authentication: Add user authentication to manage user sessions and identities.
- Persistence: Store chat history in a database to allow users to view past messages.
- Advanced Features: Implement features like typing indicators, message editing, and file sharing.