Web Development

[Next.js] per-page layout을 이용해서 백엔드 socket.io 연결하기

Leafmire 2023. 5. 16. 20:27

Next.js 를 사용하면서 별도의 백엔드에서 돌아가고 있는 socket.io 웹소켓에 연결을 할 필요가 있었다.

 

 

const socket = io("https://server-domain.com");

그런데 각페이지에서 socket 연결을 만들면 페이지 이동 시 socket의 연결이 끊어졌다 다시 재연결하는 문제가 생긴다

이 문제를 해결하기위해 useContext를 이용해보자고 생각했다.

 

 

lib/socketContext.tsx

import { Manager, Socket, io } from "socket.io-client";

interface SocketContextValue {
	chatSocket: Socket | null;
}

const SocketContext = createContext<SocketContextValue>({
	chatSocket: null,
});

interface SocketProviderProps {
	children: ReactNode;
}

먼저 SocketContext 생성에 필요한 타입을 선언하고 Context를 생성해준다. chatSocket 변수에 socket.io가 제공하는 Socket 객체를 담을 수 있도록 한다.

 

 

const SocketProvider = ({ children }: SocketProviderProps) => {
	const [chatSocket, setChatSocket] = useState<Socket | null>(null);

	useEffect(() => {
		const manager = new Manager(`${SERVER_ADDRESS}`, {
			extraHeaders: {
			  Authorization: `Bearer ${localStorage.getItem("token")}`,
			  }
		});

		const newChatSocket = manager.socket("/chat-room", {
		});

		setChatSocket(newChatSocket);

		return () => {
			newChatSocket.close();
		};
	}, []);
 
	return (
		<UsersProvider>
			<SocketContext.Provider
				value={{ chatSocket }}
			>
				{children}
			</SocketContext.Provider>
		</UsersProvider>
	);
};

다음으로 자식 컴포넌트를 감싸줄 Provider를 생성한다.

extraHeaders에 토큰을 넣어준 이유는 현재 진행중인 프로젝트는 로그인을 한 유저만 소켓연결이 가능하기 때문이다.

이때문에 return에서 SocketContext.Provider를 유저정보를 가져오는 UserProvider로 한번 더 감싸주었다.

 

pages/page.tsx

Page.getLayout = function getLayout(page: ReactElement) {
	return (
		<SocketProvider>
			<Layout>{page}</Layout>
		</SocketProvider>
	);
};

이제 각 Page의 getLayout에서 page를 Provider로 감싸주면 해당 구조를 가진 페이지로 이동할 경우 Socket 연결이 끊어지지않고 이동된다.

 

※ 참고

Routing: Pages and Layouts | Next.js (nextjs.org)

 

Routing: Pages and Layouts | Next.js

Using Pages Router Features available in /pages

nextjs.org

 

import { SocketContext } from "@/lib/socketContext";
    
const { chatSocket: socket } = useContext(SocketContext);
useEffect(() => {
	if (socket) {
		socket.on("friendList", (data) => {
			setuserData(data);
		});
	}

	return () => {
		if (socket) {
			socket.off("friendList");
		}
	}
}, [socket]);

이런식으로 page 컴포넌트들에서 자유롭게 소켓 사용이 가능하다.

다만 useEffect 내에서 사용할 경우 return에 socket.off를 통해 등록한 이벤트를 해제하지 않으면 다음 해당 페이지에 들어올때 이벤트가 여러번 발생하니 주의해야한다.