728x90
1. socket.io 설치하기
npm install socket.io socket.io-client
2. Next.js API Route에 Socket.io 서버 설정
: pages/api/socket.ts를 생성하여 Socket.io 서버를 설정합니다.
import { Server as IOServer, Socket } from "socket.io";
import { NextApiRequest, NextApiResponse } from "next";
import { Server as HTTPServer } from "http";
import { connectDB } from "@/util/database";
import { getServerSession } from "next-auth";
import { authOptions } from "../api/auth/[...nextauth]";
import { ObjectId } from "mongodb";
type NextApiResponseWithSocket = NextApiResponse & {
socket: {
server: HTTPServer & {
io?: IOServer;
};
};
};
interface ChatMessage {
chatRoomId: string;
sender: string;
text: string;
}
let io: IOServer | undefined;
export default async function handler(req: NextApiRequest, res: NextApiResponseWithSocket) {
if (!res.socket.server.io) {
console.log("Socket.io server starting...");
io = new IOServer(res.socket.server, {
cors: {
origin: "http://localhost:3000", // 클라이언트 URL
methods: ["GET", "POST"],
},
});
res.socket.server.io = io;
io.on("connection", async (socket: Socket) => {
console.log("A user connected:", socket.id);
// 세션에서 사용자 정보 가져오기
const session = await getServerSession(req, res, authOptions);
if (!session) {
console.log("Unauthorized user tried to connect");
socket.disconnect(); // 세션 없는 사용자는 연결 차단
return;
}
const client = await connectDB;
const db = client.db("StellarLink");
const requesterEmail = session.user?.email;
const requester = await db.collection("user_cred").findOne({ email: requesterEmail });
if (!requester) {
console.log("User not found in database");
socket.disconnect();
return;
}
const requesterId = requester._id.toString(); // 사용자 ID를 문자열로 저장
console.log(`User ${requesterId} (${requesterEmail}) connected`);
// Join a room
socket.on("join_room", (room: string) => {
socket.join(room);
console.log(`User ${requesterId} joined room ${room}`);
});
// Handle incoming messages
socket.on("send_message", (data: ChatMessage) => {
const message = {
...data,
sender: requesterId, // 실제 사용자 ID를 추가
};
console.log("Message received:", message);
io?.to(data.chatRoomId).emit("receive_message", message);
});
socket.on("disconnect", () => {
console.log(`User ${requesterId} disconnected`);
});
});
} else {
console.log("Socket.io server already running.");
}
res.end();
}
- 소켓이 이미 열려있다면 재생성하지 않도록 합니다.
3. Socket.io 클라이언트 설정
'use client';
import { usePathname,useSearchParams } from 'next/navigation';
import Image from 'next/image';
import { useSession } from 'next-auth/react';
import { useEffect,useState } from 'react';
import { io, Socket } from "socket.io-client";
export default function Detail() {
const { data: session, status } = useSession();
const searchParams = useSearchParams();
const pathname = usePathname();
const [chatRoomId, setChatRoomId] = useState<string | null>(null);
const [socket, setSocket] = useState<Socket | null>(null);
const [messages, setMessages] = useState<{ sender: string; text: string }[]>([]);
const [input, setInput] = useState<string>("");
// URL 파라미터에서 chatRoomId 가져오기
useEffect(() => {
const id = searchParams?.get("chatRoomId");
console.log(`
id
`,id)
if (id) {
setChatRoomId(id);
}
}, [searchParams]);
useEffect(() => {
if (chatRoomId && !socket) {
const newSocket = io("http://localhost:3000", {
path: "/api/socket", // Next.js API Route와 연결
});
setSocket(newSocket);
console.log("Socket initialized:", newSocket);
newSocket.on("connect", () => {
console.log("Connected to Socket.io server");
newSocket.emit("join_room", chatRoomId);
});
newSocket.on("receive_message", (message) => {
console.log("New message received:", message);
setMessages((prev) => [...prev, message]);
});
return () => {
newSocket.disconnect();
};
}
}, [chatRoomId, socket]);
const handleSendMessage = () => {
if (socket && input.trim() && chatRoomId) {
const message = {
chatRoomId,
text: input,
};
// 서버로 메시지 전송 (sender는 서버에서 처리)
socket.emit("send_message", message);
setMessages((prev) => [...prev, { sender: session?.user?.name|| "Me", text: input }]); // UI 업데이트
setInput(""); // 입력 초기화
}
};
return (
<div className="w-full h-full flex items-center justify-center">
{chatRoomId ?(
<div className='text-black'>
<div>
<h1>Chat Room {chatRoomId}</h1>
<div>
{messages.map((msg, index) => (
<div key={index}>
<strong>{msg.sender}</strong>: {msg.text || "No message"}
</div>
))}
</div>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleSendMessage()}
/>
<button onClick={handleSendMessage}>Send</button>
</div>
</div>
):(
<Image
src="/SVG/bigLogo.svg"
alt="select"
width={339}
height={199}
priority
className=""
/>
)}
</div>
);
}
- 우선, 채팅방 아이디가 존재하는지 검사합니다.
- 채팅방 아이디 혹은 소켓이 변경되면 소켓을 연결하고 메세지를 보낼 수 있도록 합니다.
- 이때,
const newSocket = io("http://localhost:3000", {
path: "/api/socket", // Next.js API Route와 연결
});
를 꼭 해줘야합니다.
저희가 정의한 소켓 서버가 /api/socket
에 존재하기 때문에, 꼭 path 설정을 해주세요!!! 그래야 소켓 연결가능합니다.
(필자는 이 부분에서 한 시간 넘게 헤맸습니다 ㅜㅜㅜㅜㅜ 꼭 부탁드립니다)
4. 테스트하기
728x90
'웹개발' 카테고리의 다른 글
[Next.js] router.push 한 후, 이동한 페이지의 useEffect가 실행되지 않는 이유 (0) | 2025.01.12 |
---|---|
[ Intl.DateTimeFormat ] 날짜와 시간을 로케일(지역) 및 특정 형식에 따라 포맷하기 (0) | 2025.01.07 |
[mongoDB] $ ( 연산자(operator) 또는 플레이스홀더) (0) | 2025.01.02 |
[React] Redux (중앙 상태 관리 라이브러리)를 채팅 웹앱에 실제로 적용하기 (1) | 2024.12.27 |
[Next.js] 세션정보 변경 후 프론트엔드 쪽에 바로 업데이트 되지 않을 때 (feat. authOptions) (2) | 2024.12.20 |