인턴

SNS 로그인 로직, 커스텀 훅 하나로 정리하기

jjangsh 2025. 6. 2. 12:18

Next.js를 사용해 로그인 및 계정 연결 UI를 만들다 보면 네이버, 카카오, 애플, 페이스북 등 여러 SNS 로그인 로직이 생기기 마련입니다.

초기에는 각각의 SNS에 맞게 따로따로 작성했지만, 점차 중복이 많아지고 유지보수성이 떨어지는 문제가 생깁니다.

이 포스트에서는 useSnsLogin 커스텀 훅을 통해 중복 로직을 통합한 과정을 소개합니다.

 

📍 리팩토링 전

1. 로그인 페이지

SNS 종류별로 로그인 함수를 각각 작성해야 했습니다:

const onLoginKakao = async () => { ... };
const onLoginNaver = async () => { ... };
const onLoginApple = async () => { ... };
const onLoginFacebook = async () => { ... };

UI에서 버튼마다 각각의 함수를 연결하는 식으로 사용했습니다:

<Button onClick={onLoginKakao}>카카오로 시작하기</Button>
<IconButton onClick={onLoginFacebook} />

 

 

2. 계정 연결 컴포넌트 (SnsStart)

이 페이지도 마찬가지로 각 SNS별 함수가 따로 존재했고,

switch-case 문으로 provider에 따라 분기 처리해야 했습니다:

const onStartAccount = (provider: string) => {
  switch (provider) {
    case "facebook":
      onLoginFacebook(); break;
    case "kakao":
      onLoginKakao(); break;
    // ...
  }
};

 

 

 

3. 문제점 요약

  • 중복된 axios 요청 로직
  • 중복된 try-catch + alert 패턴
  • query-string 및 redirectUri 조합 반복
  • SNS 추가/변경 시 모든 곳을 수정해야 함

 

4. 리팩토링 전략: useSnsLogin 훅 만들기

SNS 로그인 로직을 공통화한 커스텀 훅을 만들었습니다.

 

useSnsLogin.ts

const useSnsLogin = () => {
  const { showAlert } = useAlert();

  const onLogin = async (provider: string, state?: string) => {
    try {
      if (provider === "facebook") {
        if (!window.FB) return;
        window.FB.login((response: any) => {
          if (response.authResponse) {
            const loginUrl = `${redirectUri}/facebook?openAccessToken=${response.authResponse.accessToken}`;
            window.location.href = loginUrl;
          }
        }, { scope: "public_profile,email", auth_type: "reauthenticate" });
        return;
      }

      const url = qs.stringifyUrl({
        url: URL.LOGIN_SOCIAL,
        query: {
          provider: `ncp_${provider}`,
          redirectUri: encodeURIComponent(`${redirectUri}/${provider}`),
          reauthenticate: false,
          ...(state ? { state } : {}),
        },
      });

      const res = await client.get(url);
      if (res.status === 200) {
        window.open(res.data.loginUrl, "_self");
      }
    } catch (error: any) {
      await showAlert("로그인 실패", breakLine(error.response?.data?.message), {
        type: "info",
      });
    }
  };

  return { onLogin };
};

 

 

📍 리팩토링 후

1. 로그인 페이지

const { onLogin } = useSnsLogin();

const onSnsLogin = useMemo(
  () => debounce((provider: string) => onLogin(provider, goFrom), 500, {
    leading: true,
    trailing: false,
  }),
  [goFrom, onLogin]
);

버튼 핸들링도 간단하게:

<Button onClick={() => onSnsLogin("kakao")}>카카오로 시작하기</Button>
<IconButton onClick={() => onSnsLogin("apple")} />

 

 

2. SNS 계정 연결 (SnsStart)

커스텀 훅 덕분에 switch-case 없이 훨씬 간결하게 바뀌었습니다.

const { onLogin } = useSnsLogin();
const handleClick = () => {
  onLogin(account.provider);
};

<Button onClick={handleClick}>
  {account.icon}
  {account.label}
</Button>

 

 

📚 정리

리팩토링 전 리팩토링 후

SNS별로 중복된 함수 존재 하나의 onLogin(provider)로 통일
에러 처리/alert 로직 반복 커스텀 훅 내부에서 일괄 처리
UI 코드와 로직 혼재 UI는 단순 onClick 처리만 담당

 

🧠 마무리

프로젝트 규모가 커지면 로직과 UI를 분리하는 것이 점점 더 중요해집니다.

커스텀 훅은 단순한 함수 분리 이상의 가치를 제공합니다.

공통화, 재사용성, 테스트 용이성까지 모두 얻을 수 있기 때문입니다.

 

✍️ 다음 목표

  • SNS 로딩 상태 처리 및 로딩 스피너 도입
  • 로그인 실패 시 UX 개선