새소식

반응형
프로젝트

[kakao tech bootcamp] 카카오 테크 부트캠프 챗봇 프로젝트(피카부) - 1

2024.09.09
  • -
반응형

카카오테크 부트캠프(카테부) 챗봇, "피카부"는 카카오테크 부트캠프에서 진행한 팀 미션으로부터 시작된 프로젝트입니다.

 

카카오테크 부트캠프에서는 각 과정별로(풀스택, 인공지능, 클라우드) 2명씩 모여 총 6명 인원의 팀을 구성하여 6개월간 3개의 팀 미션과 각종 현업 프로젝트를 진행하는데, 이 글은 그 중 가장 첫 번째로 진행했던 팀미션의 내용을 정리한 글이 되겠습니다.

 

제가 이번 프로젝트에서 주로 맡은 부분은 프론트엔드와 약간의 백엔드였으므로 프론트엔드 개발에 대한 내용을 주로 작성 하였습니다.

 

1. 왜 '카테부' 챗봇인가?

첫 번째로 진행한 팀미션의 주제는 '카카오테크 부트캠프 정보를 제공하는 챗봇'입니다.

 

팀 미션 주제를 선정하는 방식은 게이미피케이션 기반 미션 관리 페이지인 구름 exp에 리스트업 된 팀 미션 중에서 하나를 고르는 것이었는데, 해당 미션에서 주어진 태스크들을 완수하게 되면 미션을 완료하게 되는 방식입니다. 태스크는 미션당 보통 10~20개 정도로 정해져 있지만 각 팀끼리 상의 하여 정해진 범위를 너무 벗어나지 않는 선에서 일부 태스크를 추가하여 프로젝트의 성격을 확장하는 것이 가능했습니다.

 

저희가 정했던 팀 미션의 원래 제목은 '지능형 챗봇 기반 생활 정보 제공 서비스: 다양한 생활 정보를 제공하는 지능형 챗봇 서비스 개발'이었는데요.

 

주어진 태스크는 날씨, 교통, 레스토랑 등의 API와 연동하여 데이터를 처리하여 응답하는 등 일상과 관련된 업무들로 구성되어 있었기에, 초반에 진행했던 기획 회의 결과 우리 카테부 수강생들의 점심과 근처 식당을 추천해주는 챗봇으로부터 아이디어를 발전시켜 나가기 시작했습니다.

 

1.1 Pain Point (문제 정의)

해당 아이디어에 더해 기왕 카테부 수강생이 사용하게끔 만들거면 AI 모델에 카테부에 대한 정보들을 학습시켜 그와 관련된 정보를 제공할 수 있도록 하는 것이 어떠겠냐는 아이디어가 나왔고, 실제로 이 아이디어와 관련하여서 우리 팀원들 뿐만 아니라 다른 수강생들도 분명히 느끼고 있는 문제점이 존재했었습니다.

카테부 디스코드 채널들

카카오테크 부트캠프는 디스코드를 주된 커뮤니티로 활용을 하며, 모든 공지사항이 디스코드로 안내가 되는데, 현재 해당 디스코드에는 다양한 채널이 존재하고 각 채널별로 수많은 정보들이 올라오는 탓에 이를 일일히 다 확인하기가 힘들며, 봤다고 해도 출석과 관련된 사항은 수강 과정에서 굉장히 중요한 부분이기 때문에 그 상황에 놓였을 때 결국 정확한 정보를 얻기 위해 또 다시 물어볼 수밖에 없는 상황이 발생합니다.

2일 간격으로 올라온 유사한 질문들

 

검색을 하고 싶어도 검색 키워드가 맞지 않으면 원하는 내용이 나오지 않을 수 있기 때문에 이미 여러번 올라온 질문임에도 지속적으로 중복되는 질문이 올라올 수밖에 없는데 관리자 분들은 그러한 질문들에 대해 계속해서 답변을 해주어야 하기 때문에 수강생과 관리자 모두 답변을 주고 받는데 있어 상당한 피로감을 느낄 수 있는 상황을 볼 수 있었고, 그래서 만드려는 챗봇에 이 문제를 해결할 수 있는 요소를 추가한 일명 '카테부 챗봇'이라는 주제로 프로젝트를 시작하게 되었습니다.

 

1.2 기능

기본적으로 챗봇이 제공하는 응답은 날씨에 따른 질의(오늘 날씨와 어울리는 음식 추천해줘), 실시간 교통 상황 제공과 같이 일상과 관련된 응답은 물론이며, 가장 핵심적인 기능으로는 카카오테크 부트캠프에서 제공한 문서(노션)와 디스코드 등으로부터 얻을 수 있는 각종 정책, 공지사항, 질의응답들에 대한 이해를 바탕으로 올바른 응답을 할 수 있다는 점이 있습니다.

또한 챗봇 기능과 더불어 UI 화면 상에 위젯을 배치하여 오늘 날씨와 일정, 교통과 관련된 상황은 번거롭게 챗봇에게 물어보지 않아도 확인할 수 있도록 하는 위젯 기능이 존재합니다.

 

2. 프론트엔드 개발

2.1 UI 화면

초기 UI 화면 디자인은(figma) 위와 같습니다. 왼쪽부터 로그인 화면, 챗봇 초기화면, 챗봇 채팅화면, 프롬프트(대화스타터) 설정, 프로필 설정입니다.

 

2.2 프론트엔드 기술 스택 및 라이브러리

사용한 라이브러리 및 기술 스택 중 핵심이 되는 것들에 대해 다음과 같이 정리해보았습니다.

 

TypeScript (v5) Language 정적 타입을 제공하는 JavaScript의 상위 집합 언어
Node.js (v20.4.0) Runtime JavaScript 런타임
Next.js (v14.2.5) 프레임워크 React 기반의 서버 사이드 렌더링 및 정적 사이트 생성을 위한 프레임워크
React (v18) UI 사용자 인터페이스를 구축하기 위한 JavaScript 라이브러리
React Hook Form (v7.52.2) 폼 핸들링 간단하고 유연한 폼 핸들링을 위한 React 라이브러리
@tanstack/react-query (v5.51.1) 상태관리 서버 상태 관리를 위한 React 쿼리 라이브러리
Zustand (v4.5.4) 상태관리 전역 상태 관리를 위한 가벼운 React 상태 관리 라이브러리
Tailwind CSS (v3.4.1) CSS 유틸리티 퍼스트 CSS 프레임워크(빠르고 일관된 스타일링)
ESLint (v8) Linting 코드 품질과 스타일 유지를 위한 린팅 도구

 

2.3 로그인 화면

이번 프로젝트에서는 카카오 소셜 로그인으로만 로그인을 할 수 있도록 정했기 때문에 별도의 복잡한 로그인 화면이 존재하지 않고 위 사진과 같이 버튼을 누르면 바로 카카오 로그인 페이지로 리다이렉트 하도록 만들면 됩니다.

 

레이아웃

로그인 화면은 메인 화면과 다르게 공유하는 레이아웃이 존재하지 않습니다. 그래서 부모 레이아웃이 가장 최상위 레이아웃(RootLayout)이며 최상위 레이아웃에는 별도의 컴포넌트가 존재하지 않고 해당 페이지 차제가 화면 전체를 차지하게 됩니다.

 

컴포넌트 구조

로그인 화면 UI에서 가장 신경 썼던 부분은 카카오 버튼과 해당 버튼 위의 작은 툴팁이었는데요.

 

툴팁

이러한 툴팁은 보통 유저가 로그인을 할 때 클라이언트 쪽에 저장된 정보를 바탕으로 가장 최근에 로그인한 수단을 알려주는 용도로 배치하는 경우에 많이 보이는 UI였던 것으로 기억합니다. 하지만 이를 실제로 구현을 해보려고 검색을 통해 찾아봤지만 원하는 느낌을 낼 수가 없었는데, 그래서 그냥 직접 만들기로 했습니다.

import React, { ReactNode } from "react";
​
export const Tooltip = ({
  message,
  children,
}: {
  message: string;
  children: ReactNode;
}) => {
  return (
    <div className="relative flex flex-col items-center group">
      <span className="flex justify-center">{children}</span>
      <div
        className={`absolute whitespace-nowrap bottom-full flex flex-col items-center  group-hover:flex`}
      >
        <span className="relative z-10 p-2 mb-1 text-xs leading-none whitespace-no-wrap bg-white border border-red-500 rounded-xl">
          {message}
        </span>
        <div className="z-20 w-2 h-2 -mt-2 rotate-45 bg-white border-b border-r border-red-500" />
      </div>
    </div>
  );
};
​

Tooltip 컴포넌트에서 자식 컴포넌트를 받아 해당 컴포넌트 위에 표시되도록 코드를 구성하였습니다. 중요한 부분은 말풍선의 꼬리 부분인데, 이 부분은 삼각형을 만들어 준 다음 아래 적당한 위치에 붙여줌으로써 더욱 툴팁 같이 보이게 할 수 있었습니다.

 

로그인 기능

이번 프로젝트에서는 인증 방식을 소셜 로그인(OAuth)과 세션 방식으로 구현하였습니다. 소셜 로그인은 백엔드에서 로그인 웹페이지 리다이렉트를 보내주기만 하면 세션이 만들어지면서 인증에 성공하게 되어 프론트엔드 쪽에서는 매우 간단하게 로그인 기능을 구현할 수 있습니다.

 

  const handleKakaoLogin = () => {
    window.location.href = "http://localhost:8080/oauth2/authorization/kakao";
  };

카카오 버튼을 누르면 백엔드 서버의 '/oauth2/authorization/kakao' 엔드포인트로 요청을 보내게 되고 응답으로는 카카오 로그인 페이지 html이 넘어와 해당 페이지에서 로그인을 성공하면 쿠키에 세션이 자동으로 만들어지게 됩니다.

이후에 인증이 필요한 API를 클라이언트에서 요청을 할 때에도 credentials옵션을 "include"로 주게 되면 쿠키가 같이 전송되어 백엔드 서버에서 해당 세션을 받아 사용자를 확인할 수 있기 때문에 굉장히 간편합니다.

 

2.4 사이드바(Sidebar)

로그인 페이지를 제외한 모든 페이지들이 공유하는 레이아웃은 위 사진과 같습니다.

초록색 박스와 빨간색 박스로 이루어져 있는데 초록색 박스가 모든 URL에서 공통적으로 배치되는 컴포넌트이고 각 페이지의 내용이 빨간색 박스 안으로 들어가게 됩니다.

 

컴포넌트

크게 네 부분으로 나뉘며 그 중 가장 중요한 곳은 각 대화방이 리스트로 배치되는 세 번째에 위치한 컴포넌트입니다.

 

해당 부분에는 대화방으로 진입할 수 있는 버튼들이 있고 클릭하면 router.push로 URL 리다이렉션을 시킵니다. ChatGPT에서는 이 부분이 Next.js의 Link 태그로 구현이 된 것 같은데 처음엔 저도 Link 구현을 해보려 했으나 삭제 버튼을 클릭하게 되면 원치 않게 페이지 리다이렉션이 되어 onClick 시 router.push가 동작하는 방식으로 구현하였습니다.

 

삭제 기능

삭제 버튼을 클릭하면 위 사진처럼 AlertDialog가 발생하며 삭제 전 한번 확인할 수 있는 과정을 추가하였습니다. 만약 해당 채팅방에 진입해있는 상황에서 그 채팅방을 삭제하는 경우엔 if문으로 분기하여 홈화면으로 이동하도록 따로 로직을 구현해야 할 것 같습니다.

 

현재는 현재 진입 중인 채팅방을 삭제해도 아무런 변화가 없고 새로고침을 해야지 해당 채팅방의 내역을 불러오는 react query의 useQuery가 동작하게 되고 해당 채팅방은 이제 불러올 수가 없기 때문에 오류가 발생하면서 자연스럽게 홈으로 이동하게 되어 치명적인 오류는 발생하지는 않는 상태입니다.

 

다만 UX를 위해서는 이 경우에 자동으로 홈으로 리다이렉트 하도록 하는 기능을 추가해야 할 것 같습니다.

 

++ 추가 구현 코드

const deleteChatMessage = (chatId: number) => {
  if (currentChatId === deleteInfo.id) {
    deleteChatMutate(chatId);
    router.push("/");
  } else {
    deleteChatMutate(chatId);
  }
};

url로부터 현재 진입한 chatId를 가져와 삭제하기로 선택한 id와 비교하여 로직을 다르게 구현한 모습입니다.

 

2.5 챗봇 초기 화면(InitialChat)

초기 피그마 디자인

 

컴포넌트

초기 화면에서 볼 수 있는 컴포넌트는 크게 3가지로 구분 되며, 채팅창 초기 컴포넌트, 날씨 위젯 컴포넌트, 달력 위젯 컴포넌트입니다.

여기서 달력 위젯은 팀 회의 결과 교통을 보여주는 것으로 대체하여 구현하기로 했기에 최종 구현에는 교통 위젯이 포함됩니다.

 

1차 구현

1차적으로 구현하여 저장소에 push했던 UI의 모습입니다. 우선은 배치 정도만 확인하기 위해 코드를 구현했으며 여기서 이미지 수정, 대화 스타터 기능 추가, 메시지 입력칸 수정, 위젯 배치 등 변경해야 할 사항들이 많이 보입니다...

 

2차 구현

추가 구현 후 Push를 한 버전에는 상당히 많은 기능을 추가하였고 UI도 많이 개선되었습니다. 수정, 추가된 부분은 다음과 같은 것들이 있습니다.

  • 이미지 변경: 팀원으로부터 이미지 리소스를 받아 교체
  • 위젯 너비 증가: 위젯을 강조하기 위해 사이드바의 너비를 줄이고 위젯의 너비를 증가시킴
  • 대화 스타터: 세 개로 구성된 대화 스타터를 클릭 시 해당 내용으로 대화 시작
  • 메세지 입력 칸(textarea):
    • 최대 9줄까지 컴포넌트 높이가 늘어남(9줄 이상부터는 스크롤바가 생김)
    • shift enter 시 줄바꿈 가능
    • focus 시 생기는 겉테두리 제거
    • 전송 아이콘 이쁜 걸로 변경
  • 날씨 위젯
    • 백엔드 API 요청으로 데이터를 가져옴(오늘 날씨, 주간 날씨)
    • 오늘 날씨와 주간 날씨를 보여줌
    • 퍼센트로 되어있는 데이터는 습도를 의미
  • 교통 위젯(미완료)
    • 교통 위젯에 들어갈 내용이 정해지지 않아 아직 연결이 되지 않은 상태로 데이터를 임의로 만들어 하드 코딩 해 놓은 상태임
  • 로딩 UI 변경
    • 로딩 중에 보여줄 UI를 로딩 스피너(원 모양 돌아가는)에서 스켈레톤 UI로 변경하였음

  • 스크롤바 UI 변경
    • 스크롤바 UI가 조금 투박한 느낌이 있어 스크롤바 디자인을 조금 더 깔끔하게 수정하였다.

 

나중에 쓸 만한 구현들

  • 스크롤바 변경
    • 스크롤바 변경은 브라우저 자체에서 제공하는 스크롤바를 교체하는 개념이기에 global.css에서 수정할 수 있습니다.
@layer utilities {
  .custom-scrollbar::-webkit-scrollbar {
    width: 8px;
    background-color: transparent;
  }
​
  .custom-scrollbar::-webkit-scrollbar-thumb {
    /* background: linear-gradient(
      rgb(251, 115, 165),
      rgb(89, 95, 239),
      rgb(148, 234, 226),
      rgb(252, 203, 107)
    ); */
    background-color: gray;
    border-radius: 10px;
  }
}

 

2.6 챗봇 채팅 화면

초기 피그마 디자인

 

최종 구현 모습

가장 우여곡절이 많았던 파트입니다. 생각보다 까다로운 점들이 많았고 문제를 해결하기 위해 ChatGPT의 플로우도 분석하고 해봤지만 일단은 제가 생각해낸 방식으로 우선 구현을 해보았습니다.

 

어느 정도 플로우는 유사하게 만들어낼 수 있었지만 위 영상을 보면 알 수 있듯이 여전히 문제가 남아있습니다.

 

새채팅을 만드는 과정에서 유저가 보낸 채팅과 AI가 보낸 채팅을 페이지 이동 없이 보여주다가 AI가 응답을 마치는 순간 페이지 리다이렉트를 하도록 구현을 진행하였습니다.

 

그런데 자세히 잘 보면 그 중간 과정에서 깜빡이는 효과가 생기는 것을 볼 수 있는데 두 개의 컴포넌트에서 깜빡임이 발생합니다.

  1. 새 채팅에 대한 제목 쓰레드가 생기면서 생기는 사이드바의 깜빡임(왜 생기지??)
  2. 페이지 리다이렉션을 하면서 해당 채팅방에 대한 채팅 내역을 불러오면서 메세지가 대치되면서 생기는 깜빡임

1번에 대한 부분은 왜 그런지 이유 파악 중에 있고 2번이 발생하는 이유는 다음과 같은 이유로 예상하고 있습니다.

새 채팅 입력과 AI 응답에 대한 데이터를 저장하는 곳은 Zustand로 클라이언트 쪽 상태 관리 방식입니다.

 

그런데 /chat/:chatId로 이동하면 서버 상태 관리 도구인 react query의 useQuery가 동작하면서 chatId에 대한 채팅 내역을 불러오는 fetch api가 동작하고 이 때 두 가지의 상태가 동시에 존재하는 상태가 됩니다. 즉, Zustand와 React Query의 상태가 둘 다 존재하는 상태에서 결국 화면에 보여줄 데이터는 하나만 사용해야 하는데 /chat/:chatId 페이지는 보통 기존에 존재하던 채팅방에 들어가는 경우 때문에서라도 React Query의 상태를 사용해야 할 수 밖에 없습니다.

  • 이를 해결하려면 zustand 상태가 존재하면 zustand 상태, 없으면 RQ 상태를 사용하는 방식으로 해야 합니다.

그래서 제가 적용한 방식은 페이지 리다이렉션 후에 zustand 상태는 초기화시키고 페이지가 리다이렉션 되면서 react query가 동작하면서 방금 만들어진 채팅 메세지를 불러오고 해당 메세지를 보여주게 되는데 어쨌든 이 과정에서 상태 도구의 변경이 있다보니 깜빡임이 발생하게 되는 것입니다.

 

더 정확히 말하자면 Zustand 데이터를 보여주다가 페이지 리다이렉션 이후 React query의 데이터를 보여줘야 하는데 이 데이터를 가져오는 데 시간이 걸리기 때문에 그 사이에 데이터가 존재하지 않는 갭이 발생하고 이 과정에서 채팅이 잠깐 사라지는 문제 발생하는 것이 아닌가 하는 것이 제 생각입니다.

 

이 상황이 되게 복잡한 게 zustand 상태를 그냥 그대로 끌고 가서, /chat/:chatId 페이지에서 사용하되 React query는 zustand가 존재하는 경우엔 데이터 페칭을 하지 않도록 하면 해결할 수는 있을 것 같긴한데 이렇게 되면 두 가지의 길이 생깁니다.

  1. 기존의 채팅방을 사이드바를 통해 들어간 경우: react query 상태에 데이터를 계속 추가함
  2. 새 채팅을 만들어서 계속 채팅하는 경우: zustand에 데이터를 계속 추가함, 새로고침 시 zustand 초기화 및 React query 동작(zustand 상태는 언마운트 시 초기화되기 때문)

이 방식이 그나마 제가 생각한 것 중 합리적인 방식인 것 같은데 같은 컴포넌트에서 두 개의 상태를 사용하는 이 방식이 뭔가 효율적이지 못한 것 같고 구현도 너무 복잡해 맞는 방식인지는 모르겠어서 많은 고민을 해봐야할 것 같습니다.

++ 위 방식대로(zustand가 있으면 그걸 쓰고 없으면 RQ 상태) 수정을 해보았는데 이상하게도 잘 되긴 합니다.

 

핵심 수정 사항은 보여줄 메세지 데이터에 대해 initialData(zustand)가 있으면 그걸 쓰고 없으면 react query로부터 가져오도록 하는 것이었습니다. 새 채팅을 보내고 AI 응답을 성공적으로 가져오면 resetInitialData()를 통해서 initialData가 리셋되면서 자연스럽게 react query로부터 가져온 데이터를 사용하게 되는데 이 과정에서 깜빡임이 없어졌습니다.

 

아직 정확한 이유는 모르겠어서 다음 포스팅을 진행하면서 이에 대해 더 자세히 다뤄보도록 하겠습니다.

추측하기로는 코드 상에서 페이지 라우팅 -> setTimeout(1) -> resetInitialData 과정으로 흘러가도록 설계했는데 라우팅 이후 데이터를 가져올 시간 1초를 기다린 후 resetInitialData를 하기 때문에 data를 바로 가져올 수 있어서 깜빡임 없이 부드럽게 넘어갈 수 있던 것 같았습니다.

즉, 페이지 라우팅이 된 이후 데이터 페칭이 이루어지는데 1초 동안은 Zustand의 상태 데이터를 사용하다가 zustand를 초기화시키면서 가져왔던 RQ 데이터를 사용하게 되는 방식으로 흘러가는 것입니다. 이렇게 되면 zustand에서 RQ데이터로 전환할 때 데이터가 없는 공백이 생기지 않기 때문에 깜빡임이 해소된 것으로 생각됩니다.(자세한 건 다음 포스팅에서...ㅎ)

 

추가적으로 기존에 깜빡임이 생겼던 이유 중에 하나가 react query 데이터 페칭 자체를 initialData가 존재하지 않는 경우에 되도록 했기 때문에 데이터가 없는 공백이 발생했기 때문일 것 같습니다.

 

즉, 1초 동안에 데이터를 가져오는 것이 보장이 되어야 이러한 플로우가 가능하고, 만약 데이터를 페칭하는데 1초 이상이 걸린다면 똑같이 화면이 깜빡일 것으로 추측해볼 수 있습니다.

2.7 설정 화면

 

컴포넌트

초기 디자인과 다르게 설정 화면에 진입하면 화면을 둘로 나눠 왼쪽에는 프로필 수정, 오른쪽에는 대화 스타터를 설정하는 화면이 나오도록 구성하였습니다.

 

3. 트러블 슈팅

3.1 한글로 채팅을 보내면 입력이 두 번 날아가는 문제

처음에 채팅을 보내면 내가 보낸 채팅과 AI의 채팅이 모두 두 번씩 생기는 문제가 있었습니다. 처음에는 UI적으로 문제가 있는 건가 했지만 실제로 백엔드에 요청이 두 개가 찍히는 것을 확인할 수 있었습니다.

문제의 코드는 다음과 같습니다.

<Input
    className="flex h-16 p-6 rounded-full "
    placeholder="물어보고 싶은 질문을 입력해주세요!"
    value={inputValue}
    onChange={(e) => setInputValue(e.target.value)}
    onKeyDown={(e) => {
        if (e.key === "Enter") {
            handleSubmit();
        }
    }}
/>

초기에는 Shadcn(Headless UI) 컴포넌트 중 Textarea를 사용하려 했는데 css 상의 문제로 메시지를 입력하면 텍스트가 밀린다거나 각종 문제가 생겨 Input 컴포넌트를 대신 사용했었습니다.

 

그리고 코드를 보면 onChange는 현재 input 태그로 들어오는 값을 저장하고 있는 역할을 하는데 onKeyDown은 키보드를 눌렀을 때 발생하는 이벤트를 처리하는 부분으로 여기서는 엔터를 입력했을 때 해당 내용이 전송되도록 하기 위해 위와 같이 작성하였습니다.

 

해당 버그는 신기하게도 한국어를 엔터로 입력을 전송할 때만 발생했습니다.(전송 버튼을 누르거나 엔터로 전송하더라도 영어로 보내면 문제가 없었음)

 

구글링을 해 보았을 때 onKeyDown을 onKeyPress로 바꾸면 해결이 된다고 하여 코드를 수정해보았지만, onKeyPress는 다음과 같은 특성을 가진다는 것을 알게되었습니다.

  • onKeyDown: keycode 값 - 한/영, Shift, Backspace등 인식 가능
  • onKeyPress: ASCII 값 - 한/영, Shift, Backspace등 인식 불가능

이번 프로젝트의 특성 상 메세지를 입력할 때 shift+Enter로 줄바꿈하는 기능을 반드시 넣고 싶었고 백스페이스도 당연하게 있어야 하는 기능이었기에 다른 방법을 찾아야만 했습니다.

 

문제 원인

KeyboardEvent.isComposing은 입력한 문자가 조합문자인지 아닌지를 판단합니다. 한글의 자음과 모음의 조합으로 한 음절이 만들어지는 방식이기 때문에 조합문자로 분류되지만 영어의 경우 알파벳을 나열하는 식이기 때문에 조합문자라고 볼 수 없습니다.

 

우리가 입력창에 보통 한글을 입력할 때 자세히 보면 입력 중인 글자 바로 아래 검은 밑줄 같은 것이 생기는 것을 볼 수가 있는데, 이 밑줄이 있는 상태에서 Enter 키를 입력하면 이벤트가 2번 발생하는 문제가 생기게 됩니다.

 

그 이유는 글자가 아직 조합 중인 것인지 조합이 끝난 상태인 것인지 파악하기가 어려워 이 과정에서 오류가 발생하기 때문이라고 합니다.

 

해결 과정

<textarea
  ref={textareaRef}
  rows={1}
  dir="auto"
  className="m-0 p-1 pl-4 placeholder:text-[#9B9B9B] resize-none w-full h-full bg-transparent outline-none max-h-52 overflow-y-auto"
  placeholder="물어보고 싶은 질문을 입력해주세요!"
  value={inputValue}
  onChange={(e) => {
    setInputValue(e.target.value);
  }}
  onKeyDown={(e: any) => {
    if (e.isComposing || e.keyCode === 229) return;
    if (e.key === "Enter" && !e.shiftKey) {
      e.preventDefault();
      handleSubmit();
    }
  }}
/>
  • if (e.isComposing || e.keyCode === 229) return;
    • 이 조건문은 IME(입력기) 상태에서 발생하는 이벤트를 무시하도록 합니다. IME는 일본어, 중국어, 한국어 등에서 문자를 입력할 때 사용되는 입력기입니다.
    • e.isComposing: 사용자가 IME를 사용하여 문자를 조합 중인 상태를 나타냅니다. 이 경우 이벤트 처리를 건너뜁니다.
    • e.keyCode === 229: IME 조합 중에 발생하는 키코드로, 이 경우도 이벤트 처리를 건너뛰도록 합니다.
  • If (e.key === "Enter" && !e.shiftKey)
    • 이 조건문은 사용자가 Shift키가 눌리지 않은 상태에서 "Enter" 키를 눌렀을 때 발생합니다.
    • 즉, 사용자가 단독으로 "Enter"키를 눌렀을 때만 이 조건이 참이됩니다.
  • e.prevenDefault()
    • 이 함수는 기본 이벤트 동작을 막습니다. "Enter"키를 눌렀을 때 발생할 수 있는 기본 동작(예: 텍스트 영역에서 줄바꿈)을 방지합니다.
  • Shift+Enter로 줄바꿈이 되도록 하기 위해서 input태그 대신 textarea를 사용했고 UI 밀림 현상을 해결하기 위해서 rows 속성을 1로주었습니다.

 

3.2 404 not found openresty

이 문제는 동적 라우팅(dynamic routing)에 대한 URL과 연결하려고 할 때 발생했던 문제였습니다. 다른 것들은 다 괜찮은데 유독 동적 라우트에 대해서만 이러한 문제가 발생했던 것을 확인할 수 있었습니다.

이 문제를 해결하기 위해 구글링과 ChatGPT를 다 뒤져봤는데도 해결 방법이 나오지 않아 커밋 기록을 살펴보면서 수정한 내용 중에서 해당 부분에 영향을 끼칠만한 요소를 찾아보았고 next.config.mjs 파일에 작성한 한 코드의 문제였음을 알게 되었습니다.

 

문제 원인

이전에 날씨 API를 요청하는데 CORS 문제가 발생하여 next.config.ts 파일을 수정을 했었습니다. 해결 방법은 해당 api를 요청하는 경우 동일 출처의 url로 재작성(rewrites) 해주도록 바꿨는데 이 부분에서 문제가 발생했던 것입니다.

async rewrites() {
    return [
      {
        source: "/:path*",
        destination: "<https://api.openweathermap.org/:path*>",
      },
    ];
  },

rewrites는 Next.js에서 특정 경로로 들어오는 요청을 다른 경로로 리다이렉트하는 기능을 제공하는데 여기서 source와 destination이 제대로 매핑되지 않으면서 예상치 못한 동작이 발생한 것이 원인이었습니다.

 

위 설정에서는 /api/openweathermap/:path*와 같이 구체적인 경로를 지정하지 않고, 모든 경로(/로 시작하는 경로)에 대해 rewrites를 적용하려 하여 Next.js 동적 라우팅도 결국 경로로 요청을 하는 것이기 때문에 destination이 의도치 않게 변경되어 오류가 발생했던 것입니다.

 

해결 방법

  async rewrites() {
    return [
      {
        source: "/weather/:path*",
        destination: "<https://api.openweathermap.org/:path*>",
      },
    ];
  },

해결 방법은 날씨 api의 경우, /weather를 붙여서 요청하도록 정하고 source 부분이 모든 URL에 걸리지 않도록 /weather/:path*를 명시하여 동적 라우트가 이에 걸리지 않도록 하면 됩니다.

 

다만 이러한 방식도 만약 /weather/:weatherId 와 같은 페이지가 존재하는 경우에 똑같은 문제가 발생할 수 있기에 유의해야 합니다.

물론 이 코드는 임의로 제가 데이터를 가져와서 UI를 구성하기 위해 Next.js에서 백엔드가 아닌 외부 API에 요청하느라 잠깐 발생했던 문제로 현재는 해당 과정이 존재하지 않습니다.

 

4. 이 기술을 왜 사용했는지?

4.2 Next.js

관련 기술

Next.js의 특징에 대해 알아보기 전에 전체적인 웹 렌더링 방식에 대한 지식이 있어야 합니다.

 

렌더링 방식은 크게 CSR, SSR, SSG로 나누어져 있고 각각의 특징은 다음과 같습니다.

 

CSR (Client Side Rendering)

사용자가 웹 사이트에 접속하면 브라우저는 서버에서 받은 html 파일을 화면에 로딩합니다. html에 담긴 정보는 현저히 적기 때문에 사용자는 원하는 정보를 받기까지 더 기다려야 합니다.

 

다시 브라우저는 서버에게 js 파일을 요청하게 되는데 서버에서 보내준 js 파일은 라이브러리 및 소스 코드 등을 모두 포함하기 때문에 용량이 큽니다.

 

js파일을 다운로드 하면 이후 완성된 화면을 사용자에게 보여줍니다. 만약, js 파일의 크기가 매우 크다면 사용자는 html이 그려주는 빈 화면만 보게 되는 문제점이 발생합니다.

 

낮은 SEO (Search Engine Optimization)

검색 엔진은 HTML 문서를 분석해 검색을 빠르게 할 수 있도록 도와주는 기능을 말합니다. 하지만 React와 같은 CSR 프로젝트의 index.html 코드는 body에 담고있는 내용이 매우 간략하며 그 수가 적습니다. 내용이 적기 때문에 검색 엔진들이 웹 페이지를 분석하는 데 그 결과 SEO가 상대적으로 낮습니다.

 

2. SSR (server-side Rendering)

CSR의 문제점인 사용자가 빈 화면만 보게 되는 경우를 보완하기 위해 나온 렌더링 방식입니다.

사용자가 웹 사이트에 접속하면 서버는 최소한의 js 코드를 담은 html 파일을 브라우저에 전송해 바로 사용자에게 화면으로 보여주게 됩니다. 모든 동적 요소를 담은 js 파일은 다운로드되지 않았기 때문에 사용자는 화면과 인터랙션 할 수 없습니다.

 

그 후 서버에 동적 요소를 담은 js 파일을 요청해 다운로드 후 hydration을 진행합니다. 이는 사용자에게 첫 화면을 빠르게 보여줄 수 있다는 장점이 존재합니다.

 

높은 SEO(Search Engine Optimization)

완성된 HTML 파일을 서버에서 받아오기 때문에 HTML 파일이 담고 있는 정보가 상대적으로 많습니다. 그래서 CSR보다 더 효율적인 SEO가 가능합니다.

 

3. SSG (Static Site Generation)

미리 생성한 정적인 페이지를 서버에 저장해두고 사용하는 방식입니다. 새로고침마다 새로운 데이터를 불러오며 서버에 과부하를 주는 SSR의 단점을 보완할 수 있습니다.

 

예를 들어, 모든 사용자에게 같은 화면이 보여서 굳이 동적으로 생성할 필요가 없는 블로그나 소개 페이지 등에 사용합니다.


 

React와 Next.js의 차이

React는 JavaScript 기반의 SPA 웹 프레임워크로, 컴포넌트를 십분 활용하여 UI를 더욱 쉽고 효율적으로 만들 수 있습니다.

 

중요한 점은 리액트의 CSR(Client Side Rendering), 즉 화면을 그리는 렌더링이 클라이언트에게서 발생한다는 점입니다. 서버에서 보내주는 HTML과 JavaScript 파일을 서버가 아닌, 클라이언트의 브라우저에서 페이지를 만들어줍니다. 이러한 방식은 페이지마다 서버로부터 받아오지 않아도 되기 때문에 페이지 간 전환이 부드러워진다는 장점이 있습니다.

 

하지만 HTML과 JS를 다운로드받는 동안 클라이언트는 렌더링이 불가능한, 즉 첫 페이지의 로딩속도가 느리다는 고질적인 단점을 갖고 있습니다. 또한 CSR의 특성상 빈 HTML과 JS를 보낸 뒤 클라이언트의 브라우저에서 JS를 실행시켜 화면을 그리는데, 대부분의 검색 엔진은 HTML 파일만 크롤링하는데, Meta 태그 또한 크롤링 봇이 가져가지 못해, meta 태그를 이용하는 마케팅 툴 역시 사용하지 못한다는 단점이 존재합니다. 이러한 점들은 SEO(Search Engine Optimization)에 악영향을 끼쳐 구글과 같은 검색 사이트의 상위 노출에 불리해지기 때문에 웹 비즈니스의 경우 매우 큰 단점으로 작용할 수 있습니다.

 

이러한 단점들을 극복하기 위해서 다양한 React Meta-Framework들이 나오고 있는데요.

 

대표적으로 Next.js, Gatsby, Remix 등이 있는데, 이 중 가장 활발하게 사용되는 프레임워크는 Next.js입니다. 그 이유는 2022년 10월 Next.js 13 버전 업데이트에서 굉장히 개발자들을 편리하게 도와줄 수 있는 기능들이 많이 나와 실제로 사용성, 만족도 면에서 압도적인 지표를 보여주었습니다. 웹 프레임워크는 사실 해당 프레임워크 커뮤니티 크기에 따라 좌지우지되기 때문에, 아직 인지도가 적은 타 프레임워크들보다도 더 만족도가 높고 활발한 커뮤니티를 가져 생태계를 확장해 나가고 있습니다.

Next.js는 SSR(Server-Side Rendering) 기반으로 동작하는 리액트 프레임워크입니다. SSR이란 클라이언트가 손 쓰지 않아도 즉시 렌더링 되도록 서버쪽에서 가공한 HTML 파일에 내용이 채워진 채로 보내져 브라우저가 이를 다운로드 받아 즉각 화면에 렌더링하는 방식입니다.

Next.js에서는 Code splitting이라는 기술을 통해 초기 로드 시 필요한 최소한의 코드만 다운로드하여 실행하므로, 앱의 초기 로딩 속도를 개선할뿐 아니라 동시에 React 보다 가볍게 동작할 수 있다는 장점이 있습니다. SSR이기 때문에 HTML 내용이 채워져 있다는 사실은 굉장히 훌륭한 SEO 지표를 가질 수 있다는 것을 의미하기도 합니다. 심지어는 Next 13부터 원하는 페이지에서 선택적으로 CSR도 가능하여 Next의 장점과 React의 장점을 섞어 사용할 수 있게 되었습니다.

 

Next.js의 장점

Next.js는 앞서 말한 장점과 더불어 기본적으로 서버에서 돌아가는 풀스택 프레임워크이기 때문에 백엔드 개발까지도 가능합니다. 중요한 것은 프론트엔드, 백엔드 개발 시 필요한 잡다한 설정들을 Next 프레임워크가 직접 해주기 때문에 수고를 덜어줍니다. React에서의 Routing, Babel, Webpack 등의 설정들부터, 백엔드의 middleware, SEO, SSR 등을 모두 커버하여 많은 귀찮음들을 Next가 해결해줍니다.

 

대표적인 기능은 다음과 같은 것들이 있습니다.

  • Hot reloading: 저장되는 코드를 자동으로 새로고침
  • Automatic routing: 따로 라우팅하지 않아도 pages 폴더에 있는 파일이 해당 파일 이름으로 라우팅됩니다.
  • single file components: 해당 컴포넌트만 스코프를 가지는 css를 style jsx를 통해 만들 수 있습니다.
  • server landing: 서버 렌더링 한 페이지의 소스를 보면 내부에 소스가 존재합니다.
  • code splitting: 내가 원하는 페이지에서 원하는 자바스크립트와 라이브러리 렌더링이 가능합니다.
  • typescript: 웹팩과 바벨을 수정할 필요없이 명령어만 쓰면 자동으로 타입스크립트 컴파일러가 Next.js의 타입을 가져오게 하는 next-end.d.ts 및 트랜스파일을 위한 tsconfig 등을 생성합니다.

 

정리

React를 기반으로 만든 프레임워크이기 때문에 모든 방면에서 Next.js가 React보다 우수한 결과를 낼 수 있을 것이라 생각했습니다. 실제로 Next에서는 되지만 React에서 되지 않는 것도 분명히 있기 때문이죠. 정리하면 아래와 같습니다.

 

React

React는 라이브러리로써 개발자가 코드로 직접 호출해야만 합니다. 유용한 기능을 제공하긴 하지만 나머지 기술을 모두 개발자에게 맡긴다는 문제점이 있습니다. 그래서 개발자가 React를 처음부터 구축하기 위해선 시간과 노력이 상대적으로 많이 필요합니다.

 

Next.js

Next.js는 React를 감싼 SSR 프레임워크이며, CSR과 SSR, SSG 모두 지원합니다. Next.js는 프레임워크이기 때문에 개발자의 코드를 호출하고 프레임워크의 문법과 구조에 따라야 한다는 특징이 있습니다. React에 필요한 도구를 제공하고 프로젝트에 기능 및 최적화를 제공해 줍니다.

 

SSG 방식을 사용하면 웹 사이트를 게시하기 전 페이지를 미리 생성할 수 있고, SSR을 사용하면 사용자가 요청할 때 서버 측에서 모든 코드를 실행하는 것이 가능합니다. React의 CSR 기능도 사용할 수 있어 React Hook을 사용할 수도 있습니다.

즉 Next.js는 CSR만 지원하던 React-script와 달리 CSR, SSR, SSG를 모두 지원해 React를 사용했을 때 보다 성능을 향상 시킬 수 있습니다.

 

선택 이유

  1. 개발자의 선택권을 늘려준다.
    • CSR만 주로 구현하는 React만 사용했을 때보다 CSR, SSR, SSG 등을 Next.js를 선택했을 때 렌더링 방식에 대한 개발자에게 다양한 선택지들이 주어집니다. 선택지가 늘어나 페이지마다 렌더링 방식의 차이를 주면서 개발할 수 있기 때문에 더욱 사용자의 경험을 개선할 수 있는 여지가 많아집니다.
  2. 불필요한 라이브러리 사용을 줄여준다.
    • Next.js 자체에서 제공하는 기능을 활용하면 불필요한 라이브러리 사용을 줄일 수 있습니다. 특히, 라우터 연결의 경우 파일과 URL이 일대일로 매칭되기 때문에 React-Router-Dom과 같은 라이브러리를 추가적으로 설치하지 않아도 되고 이미지 최적화 역시 Next.js에서 제공하는 다양한 기능들을 사용하여 수고를 덜 수 있다.

 

4.3 Zustand

Zustand를 떠오르는 전역 상태 관리 도구 중 하나로 그의 경쟁 상대로 Redux가 있습니다.

npm trends를 살펴봤을 때 정량적으로는 많은 차이가 나지만 추세를 봤을 때 나온지 얼마 되지 않은 라이브러리임에도 꾸준히 상승하고 있는 것을 볼 수 있습니다.

 

redux도 한 번 사용해본 적이 있는 라이브러리인데 다른 상태관리 라이브러리들 중에서 단연 제일 사용방법이 복잡한 라이브러리이며, 다른 추가 기능을 위해서 서드 파티 라이브러리(redux-toolkit, redux-thunk, redux-saga)를 설치해야하기 때문에 굉장히 무거워질 수 있는 단점이 존재합니다.

 

다만 redux는 압도적으로 많은 기업에서 사용 중인 상태 관리 라이브러리로, 그 덕에 성숙한 생태계가 구축되어 있습니다. 이 말은 개발할 때 참고 자료가 그만큼 많다는 것을 의미하기도 하죠. 또한 모든 상태 업데이트를 액션으로 정의하여 리듀서에서 이를 업데이트 하는 방식이기 때문에 상태 예측이 쉽게 예측 가능하여 유지보수 측면에서 긍정적인 효과가 있습니다.

 

Recoil은 사용해보지 않았지만 구글에 검색해본 결과 안정적인 devtools가 없어 상태를 추적하기 어렵다는 단점과, useState 훅과 비슷하게 동작하여 직관적이라는 장점들이 존재한다고 합니다.

 

이번에 사용한 zustand는 간소화된 Flux 패턴을 사용하여 작고 빠르게 확장 가능한 상태 관리 라이브러리로 다른 라이브러리들 중에서 사용 방식이 압도적으로 쉽습니다.

 

다른 React 상태 관리 라이브러리는 스토어를 주입할 때 Context API를 사용하지만 Zustand는 클로저를 활용하여 스토어 내부를 관리하기 때문에 특정 라이브러리에 종속적이지 않습니다.

 

아래와 같이 devtools로 감싸주면 redux-dev-tools로 디버깅도 가능합니다.

export const useCountStore = create<ICountStore>()(
  devtools((set) => ({
    count: 0,
    increment: () => set((state) => ({ count: state.count + 1 })),
    decrement: () => set((state) => ({ count: state.count - 1 })),
  }))
);

 

 

또 다른 특징으로는 Provider로 컴포넌트를 감싸주지 않아도 사용이 가능하며, 상태와, 그 상태를 변경하는 액션을 정의하고, 리턴 받은 hook을 어느 컴포넌트에서든 import 하여 원하는대로 사용하는 것이 가능하다는 것입니다.

 

redux와 동일한 flux 패턴으로 동작함에도, 매우 적은 코드양으로 개발이 가능하다는 장점이있습니다.

 

사용 이유 정리

Zustand가 Redux나 Context API보다 나은 이유는 아래와 같이 정리할 수 있습니다.

  • 매우 간단함
  • React Hook을 사용하여 상태를 관리하기 때문에 추가적인 Hook이 필요 없음
  • Provider로 감싸지 않아도 되기 때문에 구조가 더 단순함
  • 보일러 플레이트 코드가 적음
  • 상태가 변경될 때만 컴포넌트를 렌더링하기 떄문에 불필요한 렌더링이 줄어듦
  • 중앙 집중식, 액션 기반 상태 관리로 상태 관리를 더 체계적이고 일관성 있게 만들어 줌.

 

Redux vs. Zustand

Store를 구현하려면? (feat. React)

  • Redux - 스토어 및 상태 선언, Action 선언, Reducer 구현, Provider 연결, 컴포넌트 연결
  • Zustand - 스토어에 모두 구현, 컴포넌트에서 호출

 

Flux 패턴에 대한 이해와 러닝 커브는?

  • 두 라이브러리 모두 Flux 패턴에 영감을 받아 만들어졌지만 굉장히 복잡한 Redux와 달리 Zustand는 Flux 패턴을 단순화하여 난이도의 차이가 존재합니다.

redux에서 zustand로 라이브러리 변경을 많이 하고 있는 추세인 것으로 알고 있는데 그 이유에는 매우 경량화된 크키의 라이브러리인 것이 큰 이유이며, 최소화된 코드로 기존의 웬만한 기능들을 문제없이 대체하는 것이 가능하며, 꾸준히 관리되고 업데이트되어 많은 관심을 받기 때문인 것 같습니다.

 

다만 서버 상태 관리를 대체하기 위해서는 이후 내용인 React-Query와 결합하여 많은 사용을 하고 있습니다.

 

4.4 Tanstack Query(구 React-Query)

React-Query는 서버 상태를 페칭하여 가져온 다음 캐싱 기능 등 다양한 관리를 할 수 있는 도구입니다.

 

React 상태 관리에 대한 고민은 항상 존재해왔습니다. 그 중에는 Store가 너무 크고 복잡하다는 것과 Store에 상태 관리보다 API 호출 코드가 더 많다는 것, 비동기 통신에 적합한지의 유무 등이 존재합니다.

 

이러한 문제를 해결해줄 수 있는 대체제가 React-Query 입니다.

 

하지만 React-Query만 사용하게 되면 Store 자체는 간단하지만 컴포넌트가 복잡해지기 때문에 Client Store가 복잡하며, 비즈니스 로직이 컴포넌트에 위치하게 된다는 문제가 있기 때문에 위의 Zustand와 같이 사용하게 되면 각 라이브러리의 장점들만 사용하여 때에 맞게 사용하는 것이 가능해집니다.

 

그래서 Client State는 Zustand로, Server state는 React query로 관리하는 방안을 도입하게 되었습니다.

 

react query에는 강력한 비동기 상태 관리 도구를 지원합니다. 유용한 옵션들과 인터페이스가 존재하며, react hook 같은 간단한 사용법에 더불어 제가 사용해봤을 때 가장 강력하다고 느낀점은 react query만의 캐싱 및 동기화 기능이었습니다.

 

또한 데이터를 가져오는 Read와 데이터를 변경하는 Create, Update, Delete의 API를 각각 Query, Mutation으로 나누어 관리하기 때문에 유지보수에도 굉장히 용이합니다.

 

Redux vs. React Query

API 호출 코드에 Polling을 구현하려면?

  • Redux - Action 선언, State 추가, Reducer 대응, saga 폴링 기능 추가, 컴포넌트 연결
  • React Query - Query 선언 + 옵션 (딸.깍)

 

API 호출 상태를 알고 싶다면?

  • Redux - State 추가, Reducer 대응, 컴포넌트 연결
  • React Query - Query에서 제공

 

React-Query + Zustand

Client State 관리: Zustand

  • 컴포넌트 밖에서도 상태 변경이 가능
  • 사용성이 단순해 러닝커브가 낮음
  • 상태 관리에 필요한 코드도 적음
  • Redux Devtools 확장 프로그램 활용 가능
  • 외부 상태 관리 도구의 의존도가 낮은 코드와 전역 상태를 최소화하려는 방향성에 적합합니다.

Server State 관리: React Query

  • API 호출 코드로 비대해진 Store를 목적에 맞게 분리
  • 리액트 훅과 비슷한 직관적인 사용성
  • 여러 인터페이스 & 옵션을 제공해 적은 코드로 강력한 동작
  • 자체 개발도구 제공
  • 팀 내 도메인들이 서버와 유기적으로 얽혀져있으면서 비동기 호출 전략이 요구되므로 해당 역할에 적합합니다.

 

4.5 세션 로그인

이번 프로젝트에서는 OAuth2 방식의 카카오 로그인으로 인증을 구현하였습니다. 인증을 저장하는 방식은 크게 서버에 저장하는 방식(session), 클라이언트에 저장하는 방식(cookie, jwt) 등이 존재합니다.

 

방문자가 웹서버에 접속해 있는 상태를 하나의 단위로 보고 그것을 session이라고 부릅니다. 웹 서버는 이러한 각 단위에 session Id를 부여하게 되고 같은 브라우저인지를 구별할 수 있는 것입니다. 그래서 브라우저를 닫거나 서버에서 이 쿠키를 삭제시키면 인증이 불가능한 상태로 만들 수가 있습니다.

 

Session을 사용한다고 해서 cookie를 쓰지 않는 것은 아니며, 다만 cookie에 중요 정보를 넣어서 사용하는 방식이 아니기 때문에 우리가 통상적으로 부르는 쿠키 방식의 인증과는 차이가 있고 이를 탈취당했을 경우 단순 id일 뿐이기 때문에 더 안전하다고 볼 수 있습니다.

Spring Security를 사용하면 이러한 방식들을 API를 사용하여 쉽게 구현할 수 있습니다. 저희는 이번에 session을 사용자 브라우저의 쿠키에 session id를 저장하는 방식을 사용했습니다.

 

이러한 방식의 세션은 서버 메모리에 사용자 정보를 가지고 있는 것인데 아무래도 사용자가 많아지면 서버 메모리에 부담을 줄 수 있다는 단점이 있습니다.

 

하지만 사용자가 많은 서비스는 아니기도 하고 분산형 시스템도 아니었기 때문에 이번 서비스에서 세션방식으로 구현을 하면 세션 방식의 장점들만 최대한 활용할 수 있을 것이라 생각하였습니다.

  • 세션
    • 일정 시간동안 같은 사용자(브라우저)로부터 들어오는 일련의 요구를 하나의 상태로 보고 그 상태를 일정하게 유지시키는 기술
    • 일정 시간 -> 방문자가 웹 브라우저를 통해 웹 서버에 접속한 시점으로부터 웹 브라우저를 종료함으로써 연결을 끝내는 시점
    • 브라우저를 종료하면 종료되는 쿠키는 무슨 종류? -> expires가 Session으로 설정된 쿠키

 

장단점

  • 장점
    • 서버 측에서. 유저의 인증 정보를 관리하는 방식이기 때문에 클라이언트에 인증 정보를 관리하는 다른 방식들에 비해 보안이 강해 안전합니다.
      • 따라서 인증 정보를 저장하고 관리하는 토큰 방식과 달리 탈취될 가능성이 더 적습니다.
    • 클라이언트 측에서 인증 정보를 관리하는 경우 서버가 해당 인증을 끊거나 설정을 바꾸는 것이 불가능합니다.
  • 단점
    • 클라이언트가 요청할 때마다 서버가 직접 세션 정보를 확인해야 하기 때문에, 사용자가 많아질수록 서버의 부하가 증가하게 됩니다.
    • 서버의 메모리나 데이터베이스에 세션 정보를 저장하기 떄문에, 기본 방식에서는 서버를 확장하는 데 장애물이 될 수 있습니다.

 

 

 

 

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.