[SoundLink] SSE 연결, 싱글톤 패턴으로 관리하기

강수영·2025년 9월 24일
0
post-thumbnail

기존에는 로그인한 사용자에게만 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 훅을 호출하여, 사용자를 채팅방으로 이동시키는 구조였습니다.

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 이슈를 통해 직접 원인을 분석하고 해결하며 한 단계 더 성장할 수 있었습니다.

출처

profile
프론트엔드 개발자

0개의 댓글