기존에는 로그인한 사용자에게만 SSE 연결을 제공하기 위해, 로그인 상태를 확인하는 Private Router(컴포넌트) 내에서 useSSE
커스텀 훅을 호출했습니다. 이 방식은 사용자가 로그인하면 SSE을 연결하고, 이 연결을 통해 채팅 요청/취소,수락/거절의 알림을 수신하는 구조였습니다.
// PrivateRoute.tst
const PrivateRoute = () => {
const { isAuthenticated } = useAuthStore();
useSSE();
return isAuthenticated ? <Outlet /> : <Navigate to="/" replace />;
};
// useSSE.ts
export function useSSE() {
const navigate = useNavigate();
useEffect(() => {
// SSE 연결
const eventSource = new EventSourcePolyfill(
`${import.meta.env.VITE_API_URL}/api/alert/connect`,
{
headers: { Authorization: `Bearer ${accessToken}` },
},
);
const handleAccept = (event) => {
const { chatRoomId } = JSON.parse(event.data);
navigate(`/chatroom/${chatRoomId}`);
};
// 수락 이벤트를 받았을 때
eventSource.addEventListener('accept', handleAccept);
}, [accessToken, navigate]);
}
채팅 요청이 수락되면, 채팅 요청자에게 ‘채팅 수락’ SSE 이벤트가 전송됩니다. 이 이벤트를 수신한 클라이언트는 React Router의 useNavigate
훅을 호출하여, 사용자를 채팅방으로 이동시키는 구조였습니다.
하지만 useSSE
커스텀 훅 안에서 useNavigate
를 사용하는 방식에는 문제점이 있었습니다. 페이지 이동이 발생할 때마다useSSE
훅이 재실행되었고, PrivateRouter
컴포넌트 또한 불필요한 리렌더링이 발생했습니다.
https://github.com/remix-run/react-router/issues/7634
React Router의 GitHub 이슈를 통해, useNavigate
훅이 컴포넌트 리렌더링을 유발할 수 있다는 점을 확인했습니다. 따라서, useNavigate
훅을 실제로 이벤트 처리가 필요한 컴포넌트로 분리하여 옮기기로 결정했습니다.
싱글톤 패턴은 인스턴스가 단 하나만 생성되도록 보장하고, 그 인스턴스에 어디서든 접근할 수 있도록 하는 디자인 패턴입니다.
이를 이용해 SSE 인스턴스가 애플리케이션 전체에서 단 하나만 생성되도록 보장했습니다. 그리고 페이지 이동이 필요한 컴포넌트가 해당 싱글톤 인스턴스에 직접 접근하여 '수락' 이벤트를 처리하도록 구조를 변경했습니다.
// sseClient.ts
// SSE 인스턴스 생성하기
let es: EventSource | null = null; // 하나만 유지
export function getSSE(token: string) {
// 처음이면 생성
if (!es) {
savedToken = token;
es = new EventSourcePolyfill(SSE_URL, {
headers: { Authorization: `Bearer ${token}` },
});
return es!;
}
return es!;
}
// ChatConnectLoadingSheet.tsx
// SSE 인스턴스에 접근해 이벤트 처리
export default function ChatConnectLoadingSheet() {
const navigate = useNavigate();
useEffect(() => {
const es = getSSE(accessToken);
const onAccept = (event: any) => {
const { chatRoomId } = JSON.parse(event.data);
navigate(`/chatroom/${chatRoomId}`);
};
es.addEventListener('accept', onAccept);
}, [accessToken]);
...
}
SSE 연결 관리를 싱글톤 패턴으로 구현하고, 페이지 이동을 위한 useNavigate
훅은 이벤트 처리가 필요한 컴포넌트로 분리했습니다. 이 구조적 개선을 통해 불필요한 리렌더링을 방지하고, 성능을 최적화할 수 있었습니다.
이번 경험는 싱글톤 디자인 패턴을 학습하고 적용할 수 있었던 좋은 기회였습니다. 또한, useNavigate
의 리렌더링 문제와 같이 AI의 답변만으로는 해결이 어려웠던 문제를, 구글링과 GitHub 이슈를 통해 직접 원인을 분석하고 해결하며 한 단계 더 성장할 수 있었습니다.