Next.js App Router에서 카카오 OAuth 구현과 useSearchParams 빌드 오류 해결기

오병훈·2025년 4월 18일
0

스위프 9기 팀 프로젝트에서 카카오 OAuth 로그인을 구현하면서 겪은 과정과,
Next.js의 App Router 환경에서 발생한 빌드 오류 및 해결 과정을 정리했습니다.


1. ✅ 구현 목표

  • 로그인 버튼 클릭 시 카카오 OAuth 인증 페이지로 이동

  • 로그인 완료 후 redirect URI에 code가 포함되어 돌아옴

  • code를 백엔드로 전달하여 로그인 완료 처리

  • 로그인 후 홈으로 이동


2. 카카오 로그인 URL 생성

export const getKakaoAuthURL = () => {
  const baseUrl = "https://kauth.kakao.com/oauth/authorize";
  const config = {
    client_id: process.env.NEXT_PUBLIC_KAKAO_CLIENT_ID!,
    redirect_uri: "http://localhost:3000/oauth/kakaocallback",
    response_type: "code",
  };
  return `${baseUrl}?${new URLSearchParams(config).toString()}`;
};

🔍 설명

  • 카카오 로그인 요청 URL을 구성하는 함수
  • client_id, redirect_uri, response_type(code)을 쿼리로 포함
  • 호출 시 해당 주소로 redirect되며 카카오 로그인 페이지로 이동함

3. 로그인 버튼 구현

"use client";

import { getKakaoAuthURL } from "@/api/oauth/kakao";
import Image from "next/image";
import kakao from "@/assets/images/kakao_login.png";

export default function LoginPage() {
  const handleKakaoLogin = () => {
    window.location.href = getKakaoAuthURL();
  };

  return (
    <div className="flex h-screen items-center justify-center">
      <button
        onClick={handleKakaoLogin}
        type="button"
        className="cursor-pointer"
      >
        <Image src={kakao} alt="kakao" />
      </button>
    </div>
  );
}

🔍 설명

  • 버튼 클릭 시 로그인 URL로 이동
  • 카카오 OAuth 로그인 페이지로 리디렉션됨

4. 콜백 페이지 구성 (초기 버전 → 오류 발생)

처음엔 다음과 같이 구성했습니다:

'use client';

import { useSearchParams, useRouter } from 'next/navigation';

export default function KakaoCallbackPage() {
  const searchParams = useSearchParams(); // URL에서 code 추출
  const code = searchParams.get('code');
  const router = useRouter();

  useEffect(() => {
    if (code) {
      // code를 백엔드에 전달 후 로그인 처리
      router.push('/');
    }
  }, [code]);

  return <div>로그인 처리 중입니다...</div>;
}

❗ 문제가 된 부분

useSearchParams() should be wrapped in a suspense boundary at page "/oauth/kakaocallback"
Error occurred prerendering page "/oauth/kakaocallback"
  • useSearchParams()브라우저에서만 실행 가능한 훅인데

  • Next.js는 이 페이지가 SSG 가능하다고 판단하고 서버에서 미리 렌더링하려 시도 → 실패


4-1 해결 전략: 클라이언트 전용 컴포넌트 + CSR 강제

수정된 page.tsx (라우팅 페이지)

// app/(routes)/oauth/kakaocallback/page.tsx

export const dynamic = 'force-dynamic'; // ❗ 이 페이지는 정적 렌더링하지 마라!

import KakaoCallbackClient from '@/components/pages/oauth/KakaoCallbackClient';

export default function KakaoCallbackPage() {
  return <KakaoCallbackClient />;
}

🔍 설명

  • export const dynamic = 'force-dynamic'를 선언하면 Next.js는 SSG 시도를 하지 않음

  • 해당 선언은 import 아래에 위치해야 인식됨

  • 실질적인 클라이언트 로직은 컴포넌트로 분리함


4-2 클라이언트 전용 컴포넌트

// components/pages/oauth/KakaoCallbackClient.tsx

'use client';

import { useSearchParams, useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useOauthLogin } from '@/hooks/mutations/useOauthLogin';

export default function KakaoCallbackClient() {
  const searchParams = useSearchParams();
  const code = searchParams.get('code');
  const router = useRouter();
  const { mutate } = useOauthLogin();

  useEffect(() => {
    if (code) {
      mutate(
        { provider: 'kakao', code },
        {
          onSuccess: () => {
            router.push('/');
          },
        }
      );
    }
  }, [code]);

  return <div className="mt-20 text-center">로그인 처리 중입니다...</div>;
}

🔍 설명

  • useSearchParams()는 브라우저 환경에서만 실행되어야 하기 때문에 'use client'로 분리

  • code를 추출한 뒤 백엔드에 전달하고 로그인 상태를 갱신

  • 이후 홈으로 리디렉션


4-3. Suspense는 왜 해결이 안될까?

<Suspense>로 감싸면 다음과 같이 렌더링 순서를 지연할 수 있습니다:

<Suspense fallback={<div>로딩 중...</div>}>
  <KakaoCallbackClient />
</Suspense>

하지만 ❗ Next.js는 여전히 해당 페이지를 SSG 대상으로 간주하여
서버에서 렌더링을 시도하고 useSearchParams()를 실행 → 빌드 오류 발생

Suspense는 렌더 타이밍 조절 용도일 뿐, SSG 판단에는 영향을 주지 못함


5. 공식 문서

5-1. 📘 추가로 참고할 만한 문서

Next.js GitHub Discussion - useSearchParams 빌드 이슈

profile
Front-End Developer

0개의 댓글