Web Development
Building a Real-Time Chat Application with Next.js and WebSockets
Learn how to build a modern real-time chat application using Next.js, WebSockets, and React. Complete with user authentication, message persistence, and typing indicators.
Dev ND
September 17, 2025
15 min read
458 views
# Building a Real-Time Chat Application with Next.js and WebSockets
Real-time communication is essential for modern web applications. In this tutorial, we'll build a full-featured chat application using Next.js and WebSockets.
## Project Overview
Our chat application will include:
- Real-time messaging
- User authentication
- Message persistence
- Typing indicators
- Online user status
- Message history
## Setting Up the Project
```bash
npx create-next-app@latest chat-app
cd chat-app
npm install socket.io socket.io-client next-auth
```
## WebSocket Server Setup
Create a custom server for handling WebSocket connections:
```javascript
// server.js
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const { Server } = require('socket.io');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = createServer((req, res) => {
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
});
const io = new Server(server);
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
socket.on('join-room', (roomId) => {
socket.join(roomId);
});
socket.on('send-message', (data) => {
socket.to(data.roomId).emit('receive-message', data);
});
socket.on('typing', (data) => {
socket.to(data.roomId).emit('user-typing', data);
});
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
});
});
server.listen(3000, (err) => {
if (err) throw err;
console.log('Server running on http://localhost:3000');
});
});
```
## Client-Side Implementation
Create the chat component:
```tsx
'use client';
import { useState, useEffect, useRef } from 'react';
import io, { Socket } from 'socket.io-client';
interface Message {
id: string;
text: string;
userId: string;
username: string;
timestamp: Date;
}
export default function ChatRoom({ roomId }: { roomId: string }) {
const [socket, setSocket] = useState(null);
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState('');
const [isTyping, setIsTyping] = useState(false);
const [typingUsers, setTypingUsers] = useState([]);
const messagesEndRef = useRef(null);
useEffect(() => {
const socketInstance = io();
setSocket(socketInstance);
socketInstance.emit('join-room', roomId);
socketInstance.on('receive-message', (message: Message) => {
setMessages(prev => [...prev, message]);
});
socketInstance.on('user-typing', (data) => {
setTypingUsers(prev => [...prev, data.username]);
setTimeout(() => {
setTypingUsers(prev => prev.filter(user => user !== data.username));
}, 3000);
});
return () => {
socketInstance.disconnect();
};
}, [roomId]);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const sendMessage = () => {
if (newMessage.trim() && socket) {
const message: Message = {
id: Date.now().toString(),
text: newMessage,
userId: 'current-user',
username: 'Current User',
timestamp: new Date(),
};
socket.emit('send-message', { ...message, roomId });
setMessages(prev => [...prev, message]);
setNewMessage('');
}
};
const handleTyping = () => {
if (socket && !isTyping) {
setIsTyping(true);
socket.emit('typing', { roomId, username: 'Current User' });
setTimeout(() => setIsTyping(false), 3000);
}
};
return (
);
}
```
## Advanced Features
### Message Persistence
Store messages in a database:
```javascript
// In your socket handler
socket.on('send-message', async (data) => {
// Save to database
await saveMessage(data);
// Broadcast to room
socket.to(data.roomId).emit('receive-message', data);
});
```
### User Authentication
Integrate with NextAuth.js for secure user management.
### File Sharing
Add support for image and file uploads.
## Deployment Considerations
- Use Redis for scaling across multiple servers
- Implement rate limiting to prevent spam
- Add message encryption for security
- Monitor WebSocket connections and performance
## Conclusion
Building real-time features with Next.js and WebSockets opens up many possibilities for interactive applications. Start with this foundation and add features based on your specific needs.
{messages.map((message) => (
))}
{typingUsers.length > 0 && (
{message.username}
{message.text}
{new Date(message.timestamp).toLocaleTimeString()}
{typingUsers.join(', ')} {typingUsers.length === 1 ? 'is' : 'are'} typing...
)}
{
setNewMessage(e.target.value);
handleTyping();
}}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
placeholder="Type your message..."
className="flex-1 border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
Related Posts
You might also be interested in these posts
The Future of Web Development: Server Components and Streaming
9/19/2025•8 min read